当许多精灵在屏幕上时,Java Swing游戏的性能很差

我正在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类simple在其当前位置绘制BufferedImage。 getImage方法不会创建新的BufferedImage,它只返回Controller类的实例:

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

每个目标都运行一个Swing Timer来计算它的位置。 这是它调用的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()。

如何提高性能?

每个目标都运行一个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 } 

从本质上讲,链上的一个对象有责任跟踪游戏中的所有实体,告诉他们自己更新并渲染它们。

我认为这里可能存在的错误是你对游戏设置的整体逻辑(没有违法行为),如另一个答案所述,你有不同的计时器来处理每个实体的运动,这并不好。 我建议看一些游戏循环示例,并调整你的,你会发现一个很好的可读性和性能改进一些不错的链接:

我最初对太多计时器理论持谨慎态度。 javax.swing.Timer实例使用“单个共享线程(由执行的第一个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 http://stackoverflow.com/a/11436660/230513 */ 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. 在Swing中绘画更好(在所有情况下> = Java5)独占使用Swing Timer

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

  3. 关于一堆星和一个Swing Timer的例子

尝试为所有目标使用一个计时器。

如果您有20个目标,那么您还将同时运行20个计时器(考虑1000个目标?)。 有一些费用,最重要的是他们每个人都在做类似的工作 – 计算位置 – 你不需要拆分它们。 我想这是一个简单的任务,即使运行20次也不会让你眨眼。

如果我明白了,你想做的就是试图同时改变所有目标的位置。 您可以通过在一个线程中运行的单个方法中更改所有这些来实现此目的。