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

Java 2D平台游戏卡顿优化求助:游戏循环与重绘问题

解决Java 2D平台游戏卡顿的核心优化方案

看起来你已经精准定位到卡顿根源在游戏循环和重绘环节了,用java.util.Timer做游戏循环确实是个常见的坑——它的调度精度不足,而且运行在非事件调度线程(EDT)里,和Swing的UI操作天生不兼容,很容易引发线程冲突和画面卡顿。下面给你几个针对性的优化方案,从根本上解决问题:

1. 替换游戏循环为Swing专用的javax.swing.Timer

java.util.Timer完全不适合Swing应用,它的任务不在EDT执行,而Swing的所有UI操作(包括repaint())都必须在EDT中处理。改用javax.swing.Timer,它天生绑定EDT,既能保证调度精度,又能彻底避免线程安全问题:

// 替换原来的java.util.Timer相关代码
import javax.swing.Timer;

// 在Panel类里声明:
private Timer gameLoop;

// 在构造方法里初始化:
public Panel() {
    initComponents();
    p = new Spelare(20, 100, 50);
    väggar = new ArrayList<>();
    skapaVäggar();
    // ... 其他初始化代码 ...
    
    // 初始化Swing Timer,每11ms触发一次(约90FPS)
    gameLoop = new Timer(11, e -> gameLoopTick());
    gameLoop.start();
}

// 把原来TimerTask里的逻辑抽成单独的方法
private void gameLoopTick() {
    // 添加重力
    if (gravatyTimer <= 0) {
        p.addVsp(gravSpeed);
        gravatyTimer = 8;
    } else {
        gravatyTimer--;
    }
    // 检测碰撞
    kollaKollision();
    // 移动玩家
    p.flyttaXLed();
    p.flyttaYLed();
    
    // 现在不需要手动控制repaint频率了——Swing Timer会在EDT触发,直接调用repaint即可
    repaint();
}

2. 优化渲染性能:缓存静态元素

你的paintComponent每次都遍历所有墙体重新绘制,这是个不小的性能开销。因为墙体是静态的(不会移动),可以提前把它们绘制到一个BufferedImage里,之后每次只需要绘制这个缓存的图像,不用重复绘制每个墙体:

private BufferedImage levelBuffer;

// 在skapaVäggar()之后初始化缓存:
private void initLevelBuffer() {
    levelBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics g = levelBuffer.getGraphics();
    for (Vägg vägg : väggar) {
        vägg.rita(g);
    }
    g.dispose();
}

// 修改paintComponent:
@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    // 先绘制缓存的墙体
    g.drawImage(levelBuffer, 0, 0, this);
    // 再绘制动态的玩家
    p.rita(g);
}

记得在Panel的大小变化时(比如窗口 resize)重新生成缓存,不然会显示异常。

3. 优化碰撞检测的效率

当前的kollaKollision()每次都检查所有墙体,当墙体数量变多的时候会很慢。可以只检查玩家附近的墙体,比如根据玩家的位置计算出可能发生碰撞的墙体范围,只遍历这些墙体:

public void kollaKollision() {
    int playerX = p.getX();
    int playerY = p.getY();
    int playerSize = p.getSize(); // 假设Spelare有getSize()方法返回尺寸
    
    // 只检查玩家周围一定范围内的墙体
    for (Vägg vägg : väggar) {
        int wallX = vägg.getX();
        int wallY = vägg.getY();
        int wallSize = vägg.getSize();
        
        // 判断墙体是否在玩家的碰撞检测范围内
        if (wallX + wallSize < playerX - playerSize || 
            wallX > playerX + playerSize || 
            wallY + wallSize < playerY - playerSize || 
            wallY > playerY + playerSize) {
            continue; // 不在范围内,跳过
        }
        
        // 执行垂直碰撞检测
        if (p.placeMeetingY(p.getY() + p.getVsp(), vägg)) {
            p.setVsp(0);
        }
        // 执行水平碰撞检测
        if (p.placeMeetingX(p.getX() + p.getHsp(), vägg)) {
            p.setHsp(0);
        }
    }
}

这样可以大幅减少碰撞检测的循环次数,提升游戏循环的执行速度。

4. 确保线程安全:所有UI状态修改在EDT执行

之前用java.util.Timer时,玩家的位置修改是在非EDT线程执行的,这可能导致UI渲染时读取到不一致的状态,出现画面跳帧或卡顿。改用javax.swing.Timer后,所有游戏逻辑都在EDT执行,天然避免了这个问题。如果之后需要在其他线程处理逻辑,一定要用SwingUtilities.invokeLater()把状态修改切换到EDT。

额外小优化

  • 去掉手动控制repaintTimer的逻辑——Swing的双缓冲机制会自动处理渲染队列,频繁调用repaint()不会导致过度绘制,反而能保证画面流畅。
  • 确保Spelare和Vägg的rita()方法里没有耗时操作,比如不要在绘制时加载图片,提前把所有游戏资源加载好。

按照这些方案修改后,你的游戏卡顿问题应该会得到明显改善。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:36:48