当屏幕上有许多精灵时,Java Swing游戏性能不佳。

3
我正在使用Swing制作一个简单的塔防游戏,当我尝试在屏幕上放置许多精灵(超过20个)时遇到了性能问题。
整个游戏发生在设置了setIgnoreRepaint(true)的JPanel上。 这是paintComponent方法(其中con是Controller):
public void paintComponent(Graphics g){
    super.paintComponent(g);
    //Draw grid
    g.drawImage(background, 0, 0, null);
    if (con != null){
        //Draw towers
        for (Tower t : con.getTowerList()){
            t.paintTower(g);
        }
        //Draw targets
        if (con.getTargets().size() != 0){
            for (Target t : con.getTargets()){
                t.paintTarget(g);
            }
            //Draw shots
            for (Shot s : con.getShots()){
                s.paintShot(g);
            }
        }
    }
}

Target类在当前位置简单地绘制一个BufferedImage。getImage方法不会创建新的BufferedImage,而是简单地返回Controller类的实例:

public void paintTarget(Graphics g){
    g.drawImage(con.getImage("target"), getPosition().x - 20, getPosition().y - 20, null);
}

每个目标都运行一个Swing计时器来计算其位置。这是它调用的ActionListener:
public void actionPerformed(ActionEvent e) {
    if (!waypointReached()){
        x += dx;
        y += dy;
        con.repaintArea((int)x - 25, (int)y - 25, 50, 50);
    }
    else{
        moving = false;
        mover.stop();
    }
}

private boolean waypointReached(){
    return Math.abs(x - currentWaypoint.x) <= speed && Math.abs(y - currentWaypoint.y) <= speed;
}

除此之外,当放置新的塔时才会调用repaint()。

我该如何改善性能?

5个回答

5
每个目标都运行一个Swing Timer来计算其位置。这是它调用的ActionListener:
这可能是您的问题——让每个目标/子弹(我假设?)负责跟踪何时更新自身和绘制自身听起来需要很多工作。更常见的方法是使用类似以下形式的循环:
while (gameIsRunning) {
  int timeElapsed = timeSinceLastUpdate();
  for (GameEntity e : entities) {
    e.update(timeElapsed);
  }
  render(); // or simply repaint in your case, I guess
  Thread.sleep(???); // You don't want to do this on the main Swing (EDT) thread though
}

从根本上讲,链条中更高级别的对象有责任跟踪游戏中的所有实体,告诉它们更新自己并渲染。


听起来大家都同意这个方案。谢谢,我会改变我的设计,使用类似的东西。 - AlcoRhythms

3
我认为这里可能存在问题的是你整个游戏设置的逻辑(无意冒犯)。正如另一个回答中所述,你有不同的计时器负责每个实体的移动,这是不好的。我建议看一些游戏循环的示例,并将你的调整到这个方向,你会注意到阅读性和性能都有很大的提升。以下是一些不错的链接:

1
谢谢提供链接!你可能已经注意到了,我刚开始接触游戏开发,现在还处于试错阶段 ;) - AlcoRhythms

3

最初我对太多定时器理论持怀疑态度。使用javax.swing.Timer的实例“使用单个共享线程(由执行的第一个计时器对象创建)。”几十个甚至几十个是完全可以的,但数百个通常开始变得缓慢。根据周期和占空比,EventQueue最终会饱和。我同意其他人的观点,您需要批判性地审查您的设计,但您可能希望尝试使用setCoalesce()进行实验。供参考,这里有一个sscce可供您分析。

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;

/**
* @see https://dev59.com/F2XWa4cB1Zd3GeqPSPyl#11436660
*/
public class TimerTest extends JPanel {

    private static final int N = 25;

    public TimerTest() {
        super(new GridLayout(N, N));
        for (int i = 0; i < N * N; i++) {
            this.add(new TimedLabel());
        }
    }

    private static class TimedLabel extends JLabel {

        private static final Random r = new Random();

        public TimedLabel() {
            super("000", JLabel.CENTER);
            // period 100 to 1000 ms; frequency 1 to 10 Hz.
            Timer timer = new Timer(r.nextInt(900) + 100, new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    TimedLabel.this.setText(next());
                }
            });
            timer.setCoalesce(true);
            timer.start();
        }

        private String next() {
            return String.valueOf(r.nextInt(900) + 100);
        }
    }

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

    private void display() {
        JFrame f = new JFrame("TimerTet");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(new JScrollPane(this));
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TimerTest().display();
            }
        });
    }
}

1
太好了!性能有所提升,但我仍需要对设计进行全面改进。 - AlcoRhythms

2
  1. 如果涉及 Swing 绘制(在所有情况下都适用于 Java5 及以上版本),请专门使用 Swing Timer 进行绘制。

  2. 这个绘图过程只需要一个 Swing Timer

  3. 关于一堆星星和一个 Swing Timer 的示例,请参考此处


0

尝试使用一个计时器来处理所有目标。

如果您有20个目标,那么同时会有20个计时器在运行(想想1000个目标?)。这会带来一些开销,最重要的是它们每个都在做类似的工作--计算位置--您不需要将它们分开。我猜这是一个简单的任务,即使运行20次,也不会花费您太多时间。

如果我理解正确,您想要做的是尝试同时更改所有目标的位置。您可以通过在一个线程中运行一个单一方法来实现这一点,从而更改所有目标的位置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接