【问题标题】:Java Swing game has poor performance when many sprites are on screen当屏幕上有许多精灵时,Java Swing 游戏的性能很差
【发布时间】:2012-07-11 01:52:30
【问题描述】:

我在 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() 仅在放置新塔时调用。

如何提高性能?

【问题讨论】:

    标签: java swing animation


    【解决方案1】:

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

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

    【讨论】:

    • 听起来每个人都同意这一点。谢谢,我会改变我的设计以使用这些方面的东西。
    【解决方案2】:

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

    【讨论】:

    • 感谢您的链接!正如您可能知道的那样,我刚刚开始玩游戏,并且处于试错模式;)
    【解决方案3】:

    我最初对 too-many-timer 理论持谨慎态度。 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();
                }
            });
        }
    }
    

    【讨论】:

    • 好点!性能有所提高,但无论如何我都需要对设计进行大修。
    【解决方案4】:
    1. 在 Swing 中绘制更好(在所有情况下 >= Java5)使用 Swing Timer 完全

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

    3. 关于Stars and one Swing Timer的例子

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-01-19
        • 1970-01-01
        • 1970-01-01
        • 2016-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多