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);
}

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

我怎样才能提高性能?

4

5 回答 5

5

每个目标都运行一个摆动计时器来计算其位置。这是它调用的 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
}

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

于 2012-07-11T12:45:52.310 回答
3

我认为这里可能有问题的是您的游戏设置的整个逻辑(无意冒犯),正如另一个答案中所述,您有不同的计时器来处理每个实体的移动,这不好。我建议您看一些游戏循环示例,并根据这些示例进行调整,您会发现一些不错的链接具有极大的可读性和性能改进:

于 2012-07-11T13:03:51.420 回答
3

我最初对“太多计时器”理论持谨慎态度。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();
            }
        });
    }
}
于 2012-07-11T15:58:06.260 回答
2
  1. 在 Swing 中绘画更好(在所有情况下 >= Java5),Swing Timer仅使用

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

  3. 关于一堆星星和一个摇摆计时器的例子

于 2012-07-11T13:25:59.087 回答
0

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

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

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

于 2012-12-08T09:33:21.923 回答