【问题标题】:Java - Game is laggingJava - 游戏滞后
【发布时间】:2020-01-29 23:04:54
【问题描述】:

我正在编写一个小行星游戏,但它似乎有点滞后。我正在使用swing.Timer 来更新我的 JFrame 并显示图形。我有两个问题, 第一个是:

“计时器可能是滞后的原因吗?”第二个是:

“使用 Timer 是处理 Java 游戏编程的最佳方式,还是不是?”

在浏览网络时,似乎每个人都在使用 Timer 来处理动画,但我不禁觉得这是一种次优的方式。有人可以向我解释一下吗?提前谢谢你:)

如果有帮助,这是我的计时器的代码。首先是基类:


import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

import javax.swing.*;


public class Base implements ActionListener {

    // Attributes
    protected static int cd = 3;        // Length of Countdown in seconds
    private int nrOfAsteroids = 10;     // Amount of Asteroids spawned
    protected static int fps = 60;      // Frames-per-second

    // Various variables and constants
    protected static BufferedImage image;
    protected static int height;
    protected static int width;
    protected static boolean colorMode = false;

    // Variables needed for Key-register
    protected static boolean isWpressed = false;
    private boolean isQpressed = false;
    private boolean isEpressed = false;
    private boolean isSpacePressed = false;
    private boolean stop = false; // TODO remove after game is finished

    // Various complex-objects
    private static Base b = new Base();
    private Asteroid[] a = new Asteroid[nrOfAsteroids];
    private JFrame frame;
    private JButton start;
    private JButton colorButton;
    private JLabel dummy;
    private JLabel gameLabel;
    protected static JLabel screen = new JLabel();
    private ImageIcon icon;
    private Timer t;
    private static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();


    public static void main(String[] args) {
        height = (int) (screenSize.height * 0.9);
        width = (int) (screenSize.width * 0.9);
        image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        screen.setSize(width, height);
        b.frameSetup(); 
    } // end main

    private void frameSetup() {
        // Frame Setup
        frame = new JFrame("yaaasssss hemorrhoids");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setBackground(Color.BLACK);
        frame.setBounds((int) (screenSize.width * 0.05), (int) (screenSize.height * 0.03), width, height);
        frame.setLayout(new GridBagLayout());

        // creating a "color" button
        colorButton = new JButton("CLASSIC");
        GridBagConstraints cb = new GridBagConstraints();
        cb.weightx = 1;
        cb.weighty = 1;
        cb.gridx = 2;
        cb.gridy = 0;
        cb.anchor = GridBagConstraints.FIRST_LINE_END;
        cb.insets = new Insets(10, 0, 0, 10);
        colorButton.setPreferredSize(new Dimension(100, 30));
        frame.add(colorButton, cb);

        // creating a "ASTEROIDS" Label
        gameLabel = new JLabel("ASSTEROIDS");
        GridBagConstraints gl = new GridBagConstraints();
        gl.weightx = 1;
        gl.weighty = 1;
        gl.gridwidth = 3;
        gl.gridx = 0;
        gl.gridy = 1;
        gl.anchor = GridBagConstraints.CENTER;
        gl.fill = GridBagConstraints.BOTH;
        gameLabel.setPreferredSize(new Dimension(100, 30));
        gameLabel.setFont(gameLabel.getFont().deriveFont(60.0f));
        gameLabel.setForeground(Color.WHITE);
        gameLabel.setHorizontalAlignment(SwingConstants.CENTER);
        frame.add(gameLabel, gl);

        // Dummy Component
        dummy = new JLabel();
        GridBagConstraints dc = new GridBagConstraints();
        dummy.setPreferredSize(new Dimension(100, 30));
        dc.weightx = 1;
        dc.weighty = 1;
        dc.gridx = 0;
        dc.gridy = 0;
        frame.add(dummy, dc);

        // creating a "start" button
        start = new JButton("START");
        GridBagConstraints sb = new GridBagConstraints();
        sb.weightx = 1;
        sb.weighty = 1;
        sb.gridx = 1;
        sb.gridy = 2;
        sb.anchor = GridBagConstraints.PAGE_START;
        sb.insets = new Insets(15, 0, 0, 0);
        start.setPreferredSize(new Dimension(100, 30));
        frame.add(start, sb);

        // Implementing a function to the buttons
        start.addActionListener(this);
        colorButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (colorButton.getText() == "CLASSIC") {
                    colorMode = true;
                    colorButton.setText("LSD-TRIP");
                } else {
                    colorMode = false;
                    colorButton.setText("CLASSIC");
                }
            }

        });

        // Show Results
        frame.setVisible(true);
    }

    private void addImage() {
        // Implementing the Image
        icon = new ImageIcon(image);
        screen.setIcon(icon);
        frame.add(screen);
    }


    protected void setWindowSize() {
        width = frame.getBounds().width;
        height = frame.getBounds().height;
        screen.setSize(width, height);
    }

    @Override
    public void actionPerformed(ActionEvent ae) {
        // Cleaning the screen
        frame.remove(start);
        frame.remove(gameLabel);
        frame.remove(colorButton);
        frame.remove(dummy);

        // Checking if Window has been resized, and acting according to it
        setWindowSize();

        // Creating the image
        for (int i = 0; i < nrOfAsteroids; ++i) {
            a[i] = new Asteroid();
        }
        gameStart();
    }


    private void gameStart() {
        t = new Timer(1000/fps, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                clearScreen();
                for (int i = 0; i < nrOfAsteroids; ++i) {
                    a[i].drawAsteroid();
                }
                // Managing Controlls
                if (isWpressed) {}
                if (isQpressed) { }
                if (isEpressed) { }
                if (isSpacePressed) { }
                if (stop) { }

                // Updating the screen
                b.addImage();
            }

        });
        t.setInitialDelay(0);
        actions();
        t.start();
    }


    private void actions() {
        // Defining all the constants for more order when handling the actions
        final int focus = JComponent.WHEN_IN_FOCUSED_WINDOW;
        String move = "Movement started";
        String noMove = "Movement stopped";
        String shoot = "Shooting started";
        String noShoot = "Shooting stopped";
        String turnLeft = "Rotation left started";
        String noTurnLeft = "Rotation left stopped";
        String turnRight = "Rotation right started";
        String noTurnRight = "Rotation right stopped";
        String stopIt = "stop"; // TODO remove when game is finished

        // Getting the input and trigger an ActionMap
        screen.getInputMap(focus).put(KeyStroke.getKeyStroke("W"), move);
        screen.getInputMap(focus).put(KeyStroke.getKeyStroke("released W"), noMove);
        screen.getInputMap(focus).put(KeyStroke.getKeyStroke("SPACE"), shoot);
        screen.getInputMap(focus).put(KeyStroke.getKeyStroke("released SPACE"), noShoot);
        screen.getInputMap(focus).put(KeyStroke.getKeyStroke("Q"), turnLeft);
        screen.getInputMap(focus).put(KeyStroke.getKeyStroke("released Q"), noTurnLeft);
        screen.getInputMap(focus).put(KeyStroke.getKeyStroke("E"), turnRight);
        screen.getInputMap(focus).put(KeyStroke.getKeyStroke("released E"), noTurnRight);
        screen.getInputMap(focus).put(KeyStroke.getKeyStroke("S"), stopIt);

        // Triggered ActionMaps perform an Action
        screen.getActionMap().put(move, new AbstractAction() {
            private static final long serialVersionUID = 1L;
            @Override
            public void actionPerformed(ActionEvent e) {
                isWpressed = true;
            } });

        screen.getActionMap().put(noMove, new AbstractAction() {
            private static final long serialVersionUID = 1L;
            @Override
            public void actionPerformed(ActionEvent e) {
                isWpressed = false;
            } });

        screen.getActionMap().put(shoot, new AbstractAction() {
            private static final long serialVersionUID = 1L;
            @Override
            public void actionPerformed(ActionEvent e) {
                isSpacePressed = true;
            } });

        screen.getActionMap().put(noShoot, new AbstractAction() {
            private static final long serialVersionUID = 1L;
            @Override
            public void actionPerformed(ActionEvent e) {
                isSpacePressed = false;
            } });

        screen.getActionMap().put(turnLeft, new AbstractAction() {
            private static final long serialVersionUID = 1L;
            @Override
            public void actionPerformed(ActionEvent e) {
                isQpressed = true;
            } });

        screen.getActionMap().put(noTurnLeft, new AbstractAction() {
            private static final long serialVersionUID = 1L;
            @Override
            public void actionPerformed(ActionEvent e) {
                isQpressed = false;
            } });

        screen.getActionMap().put(turnRight, new AbstractAction() {
            private static final long serialVersionUID = 1L;
            @Override
            public void actionPerformed(ActionEvent e) {
                isEpressed = true;
            } });

        screen.getActionMap().put(noTurnRight, new AbstractAction() {
            private static final long serialVersionUID = 1L;
            @Override
            public void actionPerformed(ActionEvent e) {
                isEpressed = false;
            } });

        screen.getActionMap().put(stopIt, new AbstractAction() {
            private static final long serialVersionUID = 1L;
            @Override
            public void actionPerformed(ActionEvent e) {
                stop = true;
            } });
    } // end actions()


    private void clearScreen() {
        Graphics2D pen = image.createGraphics();
        pen.clearRect(0, 0, Base.width, Base.height);
    }


} // end class


现在是小行星类:


import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;

public class Asteroid { 

    // Attributes
    private int amountOfCornerPoints = 12;
    private int size = 50;
    private int rotationSpeed = 2;
    private int movementSpeed = 3;

    // Fields needed to construct the Asteroid
    private Polygon asteroidShape;
    private int xCenter = (int) (Math.random() * Base.width);
    private int yCenter = (int) (Math.random() * Base.height);  
    private int[] y = new int[amountOfCornerPoints];
    private int[] x = new int[amountOfCornerPoints];
    private int[] random = new int[amountOfCornerPoints];
    private int rmax = 20;              //Das Maximum für r
    private int rmin = -rmax;           //Das Minimum für r

    // Field needed to transport the Asteroid
    private boolean transporting = false;

    // Field needed to rotate the Asteroid
    private int cornerAddition = 0;

    // Fields needed to detect Collision

    // Fields needed to determine the direction of the Asteroid
    private int direction = (int) Math.round((Math.random()*7));
    private int xMove = 0;
    private int yMove = 0;

    // Fields for determining the color of the Asteroid
    private Color col;
    private int red = 255;
    private int green = 255;
    private int blue = 255;

    public Asteroid() {
        // Activating colorMode
        if (Base.colorMode == true) {
            do {
                red = (int) Math.round((Math.random()*127));
                green = (int) Math.round((Math.random()*127));
                blue = (int) Math.round((Math.random()*127));
            } while (red < 64 && green < 64 && blue < 64); }
        col = new Color(red, green, blue); 


        // Zufallszahlen Generator
        for (int i = 0; i < random.length; ++i) {
            random[i] = (int) (Math.random()*rmax + rmin); }

        asteroidShape = new Polygon();

        whichDirection();
    }


    protected void drawAsteroid() {
        move();
        rotate();
        int degreeHolder;
        int degrees;

        for (int i = 0; i < amountOfCornerPoints; ++i) {
            degreeHolder = i*(360/amountOfCornerPoints) + cornerAddition;
            if (degreeHolder >= 360) {
                degrees = degreeHolder - 360;
            } else {
                degrees = degreeHolder;
            }

            x[i] = getXvalue(size + random[i])[degrees];
            y[i] = getYvalue(size + random[i])[degrees];
        }

        asteroidShape.invalidate();
        asteroidShape = new Polygon(x, y, amountOfCornerPoints);

        Graphics2D pen = Base.image.createGraphics();
        pen.setColor(col);
        pen.draw(asteroidShape);
        pen.dispose();
    }


    private void rotate() {
        cornerAddition += rotationSpeed;
        if (cornerAddition >= 360)
            cornerAddition = cornerAddition - 360;
    }


    private void move() {
        detectTransport();
        xCenter += xMove;
        yCenter += yMove;
    }


    private void detectTransport() {
        boolean transportImmunity = false;
        if (xCenter <= -size || xCenter >= Base.width + size) {
            if (transportImmunity == false)
                transporting = !transporting;
            transportImmunity = true;
            transport();
        }
        if (yCenter <= -size || yCenter >= Base.height + size) {
            if (transportImmunity == false)
                transporting = !transporting;
            transportImmunity = true;
            transport();
        }
    }


    private void transport() {
        while (transporting) {
            xCenter -= xMove;
            yCenter -= yMove;
            detectTransport();
        }
    }


    private void whichDirection() {
        switch (direction) {
            case 0: // Gerade Oben
                xMove = 0;
                yMove = -movementSpeed;
                break;
            case 1: // Diagonal Oben-rechts
                xMove = movementSpeed;
                yMove = -movementSpeed;
                break;
            case 2: // Gerade rechts
                xMove = movementSpeed;
                yMove = 0;
                break;
            case 3: // Diagonal Unten-rechts
                xMove = movementSpeed;
                yMove = movementSpeed;
                break;
            case 4: // Gerade Unten
                xMove = 0;
                yMove = movementSpeed;
                break;
            case 5: // Diagonal Unten-links
                xMove = -movementSpeed;
                yMove = movementSpeed;
                break;
            case 6: // Gerade links
                xMove = -movementSpeed;
                yMove = 0;
                break;
            case 7: // Diagonal Oben-links
                xMove = -movementSpeed;
                yMove = -movementSpeed;
                break;
        }
    } // end WhichDirection


    private int[] getXvalue(int radius) {
        int[] xPoint = new int[360];

        for (int i = 0; i < 360; ++i) {
            double xplus = Math.cos(Math.toRadians(i+1)) * radius;
            xPoint[i] = (int) Math.round(xCenter + xplus); }
        return xPoint;  
    }

    private int[] getYvalue(int radius) {
        int[] yPoint = new int[360];

        for (int i = 0; i < 360; ++i) {
            double yPlus = Math.sin(Math.toRadians(i+1)) * radius;
            yPoint[i] = (int) Math.round(yCenter - yPlus); }
        return yPoint;  
    }

}


PS.:我的电脑很可能不是原因,因为它可以以至少 100fps 的速度运行更大的游戏

编辑:没有其他方法,例如 rotate() 方法,导致延迟,因为我已经尝试了整个代码,只使用了最基本的方法,结果是一样的。

Edit2:也许值得注意的是,延迟实际上只是几乎不明显。但是,对于像 Asteroids 这样小的游戏,确实不应该有任何延迟,尤其是如果它仅以 60 fps 运行时。

Edit3:添加了 MRE

【问题讨论】:

  • 没有正确的minimal reproducible example 很难说。我们不知道drawAsteroid() 是否在每次调用时都在读取图像,或者您如何处理旋转。所以,是的,这可能会导致延迟。
  • @Frakcool 好了,我更新了我的帖子。首先,我很高兴知道计时器本身是否有问题,特别是如果使用计时器是最好的方法。确定延迟的实际来源对我来说是第二个。
  • 这个问题(仍然)不包含@Frakcool 建议的minimal reproducible example。投票结束。
  • “我很高兴知道计时器本身是否有问题” 不,如果使用正确的话。 “如果使用计时器是最好的方法。”我们没有足够的代码结构上下文,所以无法回答这个问题。
  • 提示:如果您要回复某人,请添加@AndrewThompson 或您要回复的任何人,以便他们收到通知(@ 很重要)。它仍然不是 MRE,您阅读链接了吗?我们无法测试。但是您是 1) 创建和处理大量 Graphics 对象。 2) 创建一堆新的ImageIcons 3) 你为什么要使你的AsteroidShape 无效(它们是什么)?

标签: java swing animation timer lag


【解决方案1】:

我建议使用限制/fps 的方法,因为游戏中可能发生的延迟会被 Timer 类的严格时间间隔放大。将框架设置为可见后,添加以下代码(或类似代码):

long time = System.nanoTime();

while(!gameOver) {
    long nTime = System.nanoTime();
    float diff = (nTime - time) * 0.000000001f;

    if(diff > 1.0f / fps) {
        time = nTime;

        // do rendering here and multiply any speeds or accelerations by diff
    }
}

【讨论】:

  • 我真的很喜欢这种方法,因为它不使用计时器。你能告诉我,为什么你使用System.nanoTime()而不是System.currentTimeMillis?我想后者更容易使用。
  • 好吧,不像你做同样数量的除法/乘法并且有更高的精度,但毫秒也可以正常工作
  • 所以我只是尝试了你的建议,但现在我无法通过启动屏幕。我猜,在 while 循环期间没有更新 Windows 进程。我该如何解决?
  • 每次循环在上下文(JPanel)对象上运行时,您都必须调用 repaint()
猜你喜欢
  • 1970-01-01
  • 2013-03-12
  • 1970-01-01
  • 1970-01-01
  • 2017-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多