Java - игра отстает - PullRequest
       27

Java - игра отстает

0 голосов
/ 30 сентября 2019

Я пишу небольшую игру об Астероидах, но, похоже, она немного отстает. Я использую swing.Timer для обновления JFrame и отображения графики. У меня есть два вопроса, первый из которых:

«Может ли таймер быть причиной лагов?»и второе:

«Использует ли Таймер оптимальный способ управления игровым программированием на Java или нет?»

При просмотре интернета казалось, что все используютТаймер для обработки анимации, но я не могу не чувствовать, что это неоптимальный способ сделать это. Может кто-нибудь объяснить это мне? Заранее спасибо:)

Вот код моего Таймера, если это поможет. Сначала Базовый класс:


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: Мой компьютер, скорее всего, не причина, так как он может запускать намного большие игры спо крайней мере, 100 кадров в секунду

Редактировать: Ни один из других методов, например, метод rotate (), не вызывает задержки, так как я уже пробовал весь код только с наиболее важными методами, и результат был таким же.

Edit2: Может быть, стоит отметить, что отставание на самом деле едва заметно. Тем не менее, для такой маленькой игры, как астероиды, не должно быть никаких задержек, особенно если она работает только на 60 кадрах в секунду.

Edit3: добавлено MRE

1 Ответ

0 голосов
/ 01 октября 2019

Вместо этого я бы предложил подход с ограничением / 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
    }
}
...