首页 > 解决方案 > Java - 游戏滞后

问题描述

我正在编写一个小行星游戏,但它似乎有点滞后。我正在使用 aswing.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

标签: javaswinganimationtimerlag

解决方案


我建议使用限制/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
    }
}

推荐阅读