How do I rotate objects/images independently in Ja

2019-01-29 12:23发布

问题:

I am trying to have a series of rectangles rotating about their respective centres and moving to the right, but since the plane contains all the rectangles, all the rectangles rotate in unison about the centre of the latest added rectangle instead of independently around their respective centres. Here are the codes:

The main program:

import java.awt.Graphics2D;
import java.util.ArrayList;

public class Rectangles {

    public static final int SCREEN_WIDTH = 400;
    public static final int SCREEN_HEIGHT = 400;

    public static void main(String[] args) throws Exception {
        Rectangles game = new Rectangles();
        game.play();
    }

    public void play() {
        board.setupAndDisplay();
    }

    public Rectangles() {
        board = new Board(SCREEN_WIDTH, SCREEN_HEIGHT, this);
        rectangle_2 = new Rectangle_2();
        rectangles = new ArrayList<Rectangle_2>();
    }

    public void drawRectangles(Graphics2D g, float elapsedTime) {
        ticks++;
        if (ticks % 4000 == 0) {
            Rectangle_2 rectangle = new Rectangle_2();
            rectangles.add(rectangle);
        }
        rotateRectangles(g);
        drawRectangles(g);
        moveRectangles(elapsedTime);

        for (int i = 0; i < rectangles.size(); i++) {
            Rectangle_2 rectangle = rectangles.get(i);
            if (rectangle.getX() < -75) {
                rectangles.remove(i);
                i--;
            }
        }
    }

    public void drawRectangles(Graphics2D g) {
        for (Rectangle_2 rectangle: rectangles) {
            rectangle.drawRectangle(g);
        }
    }

    public void rotateRectangles(Graphics2D g) {
        for (Rectangle_2 rectangle: rectangles) {
            rectangle.rotateRectangle(g);
        }
    }

    public void moveRectangles(float elapsedTime) {
        for (Rectangle_2 rectangle: rectangles) {
            rectangle.move(10 * elapsedTime);
        }
    }

    private Board board;
    private Rectangle_2 rectangle_2;
    private int ticks = 0;
    private ArrayList<Rectangle_2> rectangles;
}

The rectangle class:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Rectangle_2 {

    public Rectangle_2() {
        x = 0;
        y = 200;
        rectangle = new Rectangle((int) x, (int) y, 25, 25);
    }

    public void drawRectangle(Graphics2D g) {
        g.setColor(Color.red);
        g.draw(rectangle);
    }

    public void rotateRectangle(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        angle += 0.001;
        g2.rotate(angle, rectangle.getX() + rectangle.getWidth() / 2, rectangle.getY() + rectangle.getHeight() / 2);
        g2.setColor(Color.red);
    }

    public void move(float elapsedTime) {
        x = x + elapsedTime;
        rectangle.setLocation((int) x, (int) y);
    }

    public boolean collides(Rectangle r) {
        return r.intersects(rectangle);
    }

    @Override
    public String toString() {
        return "Pipe [x = " + x + ", y = " + y + ", rectangle = " + rectangle + "]";
    }

    public Rectangle getRectangle() {
        return rectangle;
    }

    public double getX() {
        return x;
    }

    private double x;
    private double y;
    private double angle = 0;
    private Rectangle rectangle;
}

The board class where the animation takes place:

import java.awt.*;
import javax.swing.*;

public class Board extends JPanel {

    private static final long serialVersionUID = 1L;

    public Board(int width_, int height_, Rectangles simulator_) {
        width = width_;
        height = height_;
        game = simulator_;
        lastTime = -1L;
    }

    public void setupAndDisplay() {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(new JScrollPane(this));
        f.setSize(width, height);
        f.setLocation(200, 200);
        f.setVisible(true);
        this.setFocusable(true);
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        boolean first = (lastTime == -1L);
        long elapsedTime = System.nanoTime() - lastTime;
        lastTime = System.nanoTime();
        g.setColor(Color.white);
        g.fillRect(0, 0, width, height);
        g.setColor(Color.white);
        game.drawRectangles((Graphics2D) g, (first ? 0.0f : (float) elapsedTime / 1e9f));
        repaint();
    }

    private int width;
    private int height;
    private long lastTime;
    private Rectangles game;
}

Please note the rectangle takes a couple of seconds to appear because of the delay implemented. Thank you :).

回答1:

The Graphics context is a shared resource, that is, during a single paint cycle, all the components get the same Graphics context. Any changes you make to the Graphics context are also maintained (or compound in the case of same transformations). So this means, each time you call Graphics#rotate, you are actually compounding any of the previous rotations which might have been executed on it.

You need to change you code in two ways...

  1. You need a independent update cycle, independent of the paint cycle
  2. Create a local copy of the Graphics context BEFORE you apply any transformations

For example...

Rectangles becomes the main driver/engine. It's responsible for managing the entities and updating them each cycle. Normally, I'd use some kind of interface that described the functionality that other case might be able to use, but you get the idea

public class Rectangles {

    public static void main(String[] args) throws Exception {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }
                Rectangles game = new Rectangles();
                game.play();
            }
        });
    }

    public void play() {

        Board board = new Board(this);

        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(board);
        f.pack();
        f.setLocation(200, 200);
        f.setVisible(true);
        Timer timer = new Timer(40, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateRectangles();
                board.repaint();
                lastTime = System.nanoTime();
            }
        });
        timer.start();
    }

    public Rectangles() {
        rectangle_2 = new Rectangle_2();
        rectangles = new ArrayList<Rectangle_2>();
    }

    protected void updateRectangles() {
        boolean first = (lastTime == -1L);
        double elapsedTime = System.nanoTime() - lastTime;
        elapsedTime = (first ? 0.0f : (float) elapsedTime / 1e9f);

        ticks++;
        if (ticks <= 1 || ticks % 100 == 0) {
            Rectangle_2 rectangle = new Rectangle_2();
            rectangles.add(rectangle);
        }

        rotateRectangles();
        moveRectangles(elapsedTime);


        for (int i = 0; i < rectangles.size(); i++) {
            Rectangle_2 rectangle = rectangles.get(i);
            if (rectangle.getX() < -75) {
                rectangles.remove(i);
                i--;
            }
        }
    }

    public void drawRectangles(Graphics2D g) {
        for (Rectangle_2 rectangle : rectangles) {
            rectangle.drawRectangle(g);
        }
    }

    protected void rotateRectangles() {
        for (Rectangle_2 rectangle : rectangles) {
            rectangle.rotateRectangle();
        }
    }

    protected void moveRectangles(double elapsedTime) {
        for (Rectangle_2 rectangle : rectangles) {
            rectangle.move(10 * elapsedTime);
        }
    }

    private long lastTime = -1L;
    private Rectangle_2 rectangle_2;
    private int ticks = 0;
    private ArrayList<Rectangle_2> rectangles;
}

Board becomes nothing more then a surface onto which the entities can be rendered

public class Board extends JPanel {

    public Board(Rectangles engine) {
        game = engine;
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(400, 400);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        game.drawRectangles((Graphics2D) g);
    }

    private Rectangles game;
}

And Rectangle_2 is a simple container of information which knows how to paint itself when given the chance. You will note the movement and rotation methods just update the state, they do nothing else.

drawRectangle first creates a copy of the supplied Graphics2D context, before it applies it's changes and renders the rectangle, when done, it calls dispose to dispose of the copy

public class Rectangle_2 {

    public Rectangle_2() {
        x = 0;
        y = 200;
        rectangle = new Rectangle((int) x, (int) y, 25, 25);
    }

    public void drawRectangle(Graphics2D g) {
        Graphics2D g2 = (Graphics2D) g.create();
        g2.rotate(angle, rectangle.getX() + rectangle.getWidth() / 2, rectangle.getY() + rectangle.getHeight() / 2);
        g2.setColor(Color.red);
        g2.draw(rectangle);
        g2.dispose();
    }

    public void rotateRectangle() {
        angle += 0.001;
    }

    public void move(double elapsedTime) {
        x = x + elapsedTime;
        rectangle.setLocation((int) x, (int) y);
    }

    public boolean collides(Rectangle r) {
        return r.intersects(rectangle);
    }

    @Override
    public String toString() {
        return "Pipe [x = " + x + ", y = " + y + ", rectangle = " + rectangle + "]";
    }

    public Rectangle getRectangle() {
        return rectangle;
    }

    public double getX() {
        return x;
    }

    private double x;
    private double y;
    private double angle = 0;
    private Rectangle rectangle;
}