You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swing Pong游戏独立GameLoop类调用repaint()无效问题排查

问题根源分析

你的代码里repaint()无效的核心原因是实例不共享:

  • 在GUI类的构造方法中,你创建了一个new Display()并添加到窗口,这是实际显示在屏幕上的Display实例。
  • 而在Gameloop类的构造方法中,你又重新new Display()创建了一个全新的Display对象,这个对象从未被添加到任何窗口,是完全“离线”的组件。

当你在Gameloop中调用这个离线Display的repaint()时,只会更新这个看不见的组件,对窗口上真正的Display没有任何影响。此外你还存在Handler实例不共享的问题:Display和Gameloop各自创建了独立的Handler,两者的数据完全不互通,就算实例共享了,数据也不会同步。

补充Swing线程规则:Swing组件的所有更新操作(包括repaint())都应该在**事件调度线程(EDT)**中执行。你用普通Thread调用repaint()虽不会直接导致失效,但可能引发线程安全问题;而Swing的Timer默认就在EDT中执行任务,这也是之前把Timer放在Display里有效的原因之一,但你之前用Timer时同样犯了实例不共享的错误,所以还是无效。

实现方案(保留Gameloop类的Timer)

1. 共享正确的Display和Handler实例

修改代码,让Gameloop接收已经创建好的、添加到窗口的Display实例,同时共享同一个Handler:

  • 修改Main类,传递共享实例:
package pong;

public class Main {
    public static void main(String args[]) {
        new Main().start();
    }
    
    public void start() {
        Handler handler = new Handler();
        Display display = new Display(handler); // 让Display接收共享的Handler
        new GUI(display); // 把Display传给GUI
        new Gameloop(display, handler); // 把Display和Handler传给Gameloop
    }
}
  • 修改Display类,通过构造方法接收Handler:
package pong;
import java.awt.*;
import javax.swing.*;

public class Display extends JPanel {
    private static final long serialVersionUID = 1L;
    int PANEL_WIDTH = 584;
    int PANEL_HEIGHT = 362;
    private int BALL_X;
    private int BALL_Y;
    private int BALL_SIZE;
    private int BALL_X_VELOCITY;
    private int BALL_Y_VELOCITY;
    private int PLAYER_WIDTH;
    private int PLAYER_HEIGHT;
    private int SELF_X;
    private int SELF_Y;
    private int OP_X;
    private int OP_Y;
    boolean gameRunning = true;
    Handler handler;
    
    // 修改构造方法,接收外部的Handler
    Display(Handler handler) {
        this.handler = handler;
        getConcreteValues();
    }
    
    // 其他方法不变
    public void getConcreteValues() {
        BALL_SIZE = handler.getBallSize();
        PLAYER_WIDTH = handler.getPlayerWidth();
        PLAYER_HEIGHT = handler.getPlayerHeight();
    }
    
    public void getVariableValues() {
        int[] recievedData = handler.getData();
        BALL_X = recievedData[0];
        BALL_Y = recievedData[1];
        BALL_X_VELOCITY = recievedData[2];
        BALL_Y_VELOCITY = recievedData[3];
        SELF_X = recievedData[4];
        SELF_Y = recievedData[5];
        OP_X = recievedData[6];
        OP_Y = recievedData[7];
    }
    
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.DARK_GRAY);
        g.fillRect(0, 0, PANEL_WIDTH, PANEL_HEIGHT);
        g.setColor(Color.WHITE);
        g.fillOval(BALL_X, BALL_Y, BALL_SIZE, BALL_SIZE);
        g.fillRect(SELF_X, SELF_Y, PLAYER_WIDTH, PLAYER_HEIGHT);
        g.fillRect(OP_X, OP_Y, PLAYER_WIDTH, PLAYER_HEIGHT);
    }
}
  • 修改GUI类,接收已创建的Display:
package pong;
import javax.swing.*;

public class GUI extends JFrame {
    // 接收外部的Display
    GUI(Display display) {
        setTitle("Pong");
        setSize(600, 400);
        setResizable(false);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 建议用常量而非数字3
        add(display);
        setLocationRelativeTo(null);
        setVisible(true);
    }
}

2. 用Swing Timer实现Gameloop

修改Gameloop类,使用Swing Timer替代普通Thread,确保在EDT中执行更新逻辑:

package pong;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Gameloop {
    private Timer gameTimer;
    private Display display;
    private Handler handler;
    
    // 接收共享的Display和Handler
    Gameloop(Display display, Handler handler) {
        this.display = display;
        this.handler = handler;
        
        // 初始化Swing Timer,每16ms触发一次(约60帧)
        gameTimer = new Timer(16, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // 更新数据
                display.getVariableValues();
                // 触发重绘,此时操作的是窗口上的Display实例
                display.repaint();
            }
        });
        gameTimer.start();
    }
}

额外注意事项

  • 永远不要在Swing中创建多个独立的UI组件实例来操作同一个界面,必须确保操作的是已经添加到容器中并显示的那个实例。
  • 所有Swing组件的更新、重绘操作都要在EDT线程执行,Swing Timer是实现游戏循环的首选方式,因为它天然适配EDT。
  • 共享数据模型(比如你的Handler)时,要注意线程安全:如果后续加入网络操作,需要确保多线程下数据的一致性,可以用同步块或者线程安全的容器。

内容的提问来源于stack exchange,提问作者SomeoneMissing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 15:29:58