【问题标题】:Multiple moving Graphics in Java JFrameJava JFrame中的多个移动图形
【发布时间】:2025-12-24 08:00:06
【问题描述】:

我对 Java 还很陌生,现在正在学习游戏设计。我有点刚开始,所以它不是真正的游戏。 我正在处理一个线程和一个数字数组。我必须让多个正方形同时在屏幕上移动。我的代码在一个方块上运行良好,但是当我尝试将它实现到多个方块时遇到了麻烦。我正在使用两个接口,但我不知道如何使用数组来制作单个随机正方形的多个案例。任何帮助将不胜感激。 伙计们干杯。

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

public class MovingSquares extends JFrame implements Runnable
{
    private static final Dimension WindowSize = new Dimension(600, 600);    
    private static final int NUMGAMEOBJECTS = 30;
    private GameObject[] GameObjectsArray = new GameObject[NUMGAMEOBJECTS];
    static int strtCoX = (int) (Math.random() * 600);
    static int strtCoY = (int) (Math.random() * 600);


    public MovingSquares() {
        this.setTitle("Crazy squares");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Dimension screensize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
        int x = screensize.width/2 - WindowSize.width/2;
        int y = screensize.height/2 - WindowSize.height/2;
        setBounds(x, y, WindowSize.width, WindowSize.height);
        setVisible(true);

        Thread t = new Thread(this);
        t.start();      
    }

    public void run() {
        while (true) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) { }
            GameObject.move();
            this.repaint();
        }   
    }

    public void paint(Graphics g) {
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, WindowSize.width, WindowSize.height);

        int size = 50;
        int R = (int) (Math.random() * 256);
        int G = (int) (Math.random() * 256);        
        int B = (int) (Math.random() * 256);
        Color c = new Color(R, G, B);
        g.setColor(c);
        g.fillRect(strtCoX, strtCoY, size, size);
    }

    public static void main(String[] args) {
        MovingSquares M = new MovingSquares();
    }
}

第二个接口 - GameObject 类。

import java.awt.*;

public class GameObject 
{
    private static double x;
    private static double y;
    private Color c;
    public static int ranCoX = (int) (Math.random() * 20);
    public static int ranCoY = (int) (Math.random() * 20);

    public GameObject() {
        int strtX = (int) x;
        int strtY = (int) y;
        int speedX = (int) (Math.random() * 10);
        int speedY = (int) (Math.random() * 10);
    }

    public static void move() {
        int velX = (int) (Math.random() * ranCoX);
        int velY = (int) (Math.random() * ranCoY);

        if (MovingSquares.strtCoY > 600)
            MovingSquares.strtCoY = MovingSquares.strtCoY - 550;
        else if (MovingSquares.strtCoX > 600)
            MovingSquares.strtCoX = MovingSquares.strtCoX - 550;

        MovingSquares.strtCoX += velX;
        MovingSquares.strtCoY += velY;
    }

    public static void paint(Graphics g) {
        int size = 50;
        x = (Math.random() * 600);
        y = (Math.random() * 600);
        int R = (int) (Math.random() * 256);
        int G = (int) (Math.random() * 256);        
        int B = (int) (Math.random() * 256);
        Color c = new Color(R, G, B);
        g.setColor(c);
        g.fillRect((int)x, (int) y, size, size);
    }
}

【问题讨论】:

  • 建议:不要使用线程来操纵元素的位置——而是在重新绘制时计算它们的位置。
  • 感谢您的建议,但我正在学习线程,这就是要求完成的方式。为对象(正方形)运行单个线程。主要问题是将我的代码实现到给定的数组中。
  • 顺便说一句 - repaint(); 调用需要在事件调度线程上。要将其放回 EDT,请从 SwingUtiliities.invokeLater(..) 调用它。正如@slartidan 所建议的那样,这种事情在 Swing Timer 中是自动的。主 UI 也应该在 EDT 上构建(main(..) 中的内容)。

标签: java arrays swing object graphics


【解决方案1】:

这里有一个版本的移动方块,希望能帮助您了解如何制作动画。

我做的第一件事就是将这些类分成模型类、视图类或控制器类。 model / view / controller pattern 有助于分离关注点。

让我们看看你的模型类的修改版本,GameObject 类。

package com.ggl.moving.squares;

import java.awt.Color;
import java.awt.Graphics;

public class GameObject {

    private static final int size = 50;

    private double x;
    private double y;

    private double dx;
    private double dy;

    private int drawingWidth;

    public GameObject(int drawingWidth) {
        x = Math.random() * drawingWidth;
        y = Math.random() * drawingWidth;
        dx = Math.random() * 30D - 15D;
        dy = Math.random() * 30D - 15D;
        this.drawingWidth = drawingWidth;
    }

    public void move() {
        int lowerLimit = size;
        int upperLimit = drawingWidth - size;

        x += dx;
        if (x < lowerLimit) {
            x += upperLimit;
        } else if (x > upperLimit) {
            x -= upperLimit;
        }

        y += dy;
        if (y < lowerLimit) {
            y += upperLimit;
        } else if (y > upperLimit) {
            y -= upperLimit;
        }
    }

    public void draw(Graphics g) {
        g.setColor(generateRandomColor());
        g.fillRect((int) x, (int) y, size, size);
    }

    private Color generateRandomColor() {
        int R = (int) (Math.random() * 256);
        int G = (int) (Math.random() * 256);
        int B = (int) (Math.random() * 256);
        Color c = new Color(R, G, B);
        return c;
    }
}

只剩下一个静态字段,即正方形的大小。其他一切都是动态变量或方法。这允许我们创建多个 GameObject 实例。

我将绘图方法的名称从paint 更改为draw。这样我们就不会混淆哪些方法是 Swing 方法,哪些方法是我们的​​方法。

我们将绘图面板的宽度传递给构造函数。这样,我们只需要在一个地方定义宽度。您可以在 move 方法中看到,我们允许在绘图区域中留出正方形大小的边距。

构造函数定义了一个随机的初始位置和初始速度。移动方法只是保持正方形沿直线移动。

接下来,让我们看看修改后的主类,MovingSquares 类。

package com.ggl.moving.squares;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class MovingSquares implements Runnable {
    private static final int DRAWING_WIDTH = 600;
    private static final int NUMGAMEOBJECTS = 30;
    private GameObject[] gameObjectsArray = new GameObject[NUMGAMEOBJECTS];

    private JFrame frame;

    private MovingPanel movingPanel;

    private ObjectsRunnable objectsRunnable;

    public MovingSquares() {
        for (int i = 0; i < gameObjectsArray.length; i++) {
            gameObjectsArray[i] = new GameObject(DRAWING_WIDTH);
        }
    }

    @Override
    public void run() {
        frame = new JFrame();
        frame.setTitle("Crazy squares");
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                exitProcedure();
            }
        });

        movingPanel = new MovingPanel(gameObjectsArray, DRAWING_WIDTH);
        frame.add(movingPanel);

        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);

        objectsRunnable = new ObjectsRunnable(this, gameObjectsArray);
        new Thread(objectsRunnable).start();
    }

    private void exitProcedure() {
        objectsRunnable.setRunning(false);
        frame.dispose();
        System.exit(0);
    }

    public void repaintMovingPanel() {
        movingPanel.repaint();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new MovingSquares());
    }
}

我们在这里定义了绘图面板的宽度,以及一个存放游戏对象的数组。

我们通过调用 SwingUtilities invokeLater 方法在 Event Dispatch thread (EDT) 上启动 Swing 应用程序。 Swing 应用程序必须始终在 EDT 上启动。

我们在构造函数中创建游戏对象,并在 run 方法中创建 Swing 组件。我将绘图面板移到了它自己的类中,我们稍后会谈到。

我们使用窗口侦听器,以便在应用程序完成后停止线程。

我们打包 JFrame。我们指定大小的唯一地方是创建绘图面板时。我们使用 JFrame。您扩展 Swing 组件或任何 Java 类的唯一时间是您想要覆盖其中一种方法。

我们来看看绘图面板类,MovingPanel 类。

package com.ggl.moving.squares;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.JPanel;

public class MovingPanel extends JPanel {

    private static final long serialVersionUID = -6291233936414618049L;

    private GameObject[] gameObjectsArray;

    public MovingPanel(GameObject[] gameObjectsArray, int width) {
        this.gameObjectsArray = gameObjectsArray;
        setPreferredSize(new Dimension(width, width));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());

        for (int i = 0; i < gameObjectsArray.length; i++) {
            gameObjectsArray[i].draw(g);
        }
    }

}

我们重写paintComponent 方法以在JPanel 上绘图。由于游戏对象会自己绘制,因此我们在每个游戏对象上调用 draw 方法。

最后我们来看看动画runnable,ObjectsRunnable类。

package com.ggl.moving.squares;

import javax.swing.SwingUtilities;

public class ObjectsRunnable implements Runnable {

    private volatile boolean running;

    private GameObject[] gameObjectsArray;

    private MovingSquares movingSquares;

    public ObjectsRunnable(MovingSquares movingSquares,
            GameObject[] gameObjectsArray) {
        this.movingSquares = movingSquares;
        this.gameObjectsArray = gameObjectsArray;
        this.running = true;
    }

    @Override
    public void run() {
        while (running) {
            updateObjects();
            xxx();
            sleep();
        }
    }

    private void updateObjects() {
        for (int i = 0; i < gameObjectsArray.length; i++) {
            gameObjectsArray[i].move();
        }
    }

    private void xxx() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                movingSquares.repaintMovingPanel();
            }
        });
    }

    private void sleep() {
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
        }
    }

    public synchronized void setRunning(boolean running) {
        this.running = running;
    }

}

这是一个简单的动画或游戏循环。我将重绘调用放在另一个 SwingUtilities 的 invokeLater 方法中,以确保在 EDT 上更新 Swing 组件。

希望对你有帮助。

【讨论】:

  • 我喜欢你放下的布局。谢谢您的帮助。它比我现在所知道的要复杂一些,但我会在更新我的知识时继续检查它。一个问题。我的版本所有的方块都是相同的颜色,但我在你的屏幕截图中看到它们有多种颜色,你是怎么做到的?再次感谢。
  • @cg-man:在绘制每个对象时颜色是随机的。查看 GameObject 类的 generateRandomColor 方法。
  • 啊,我明白了,我的代码和你的不同,所以改变它还不够,但我注意到我根本没有引用 draw 方法,只是在不同的坐标处重新绘制相同的颜色。干杯吉尔伯特你是明星。 :)
【解决方案2】:

静态变量在每个运行时只能使用一次。要创建多个 GameObjects,您必须避免使用 static 关键字。

然后多次调用new GameObject() 来创建多个 GameObject 实例,每个实例都有自己的一组变量。


编辑:

  1. 将变量 strtCoXstrtCoY 移动到 GameObject(如 @andrew-thomson 所建议的那样)
  2. 修复所有对strtCoXstrtCoY 的引用在GameObject
  3. g.fillRect(strtCoX, strtCoY, size, size); 更改为for (GameObject currentObject : GameObjectsArray) g.fillRect(currentObject.strtCoX, currentObject.strtCoY, size, size);

说明:坐标是GameObject的属性。每个游戏对象都应该有自己的坐标。

=> 你的代码应该和以前一样工作

  1. 删除GameObject中的所有static关键字
  2. GameObject.move(); 更改为for (GameObject currentObject : GameObjectsArray) currentObject.move();
  3. 像这样在public MovingSquares() 构造函数中初始化你的GameObjectArray:for (int i = 0; i &lt; GameObjectsArray.length; i++) GameObjectsArray[i] = new GameObject();

解释:使用new GameObject(),我们创建了 30 个实例(在构造函数内的那个循环中)。因此我们还必须调用move() 30 次(这就是为什么它也嵌套在一个循环中)

=> 戴太阳镜!

【讨论】:

  • 此外,GameObject 类的move() 方法不应引用(或更改)来自MovingSquares 类的值!相反,每个GameObject 实例只应更改它们自己的值,然后在使用GameObjectpaint(Graphics) 方法时使用这些值(在该代码中永远不会)。
  • 好的,我明白你的意思,但我不确定我应该如何调用 GameObject 类。每次我尝试它时它都说它不能调用非静态方法或类似的东西。请原谅我的含糊不清我只是在学习java。我如何将 GameObjectsArray 合并到 Moving Squares 类中?
  • @slartidan 我如何调用 new GameObject() 多次?
  • @slartidan Thabks 非常感谢您的帮助,我不知道如何实现阵列,但现在我明白了。干杯,很大的帮助。