爪哇 - 游戏滞后



我正在编写一个小小行星游戏,但它似乎有点滞后。我正在使用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.:我的电脑很可能不是原因,因为它可以运行至少 100fps 的更大游戏

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

编辑2:也许值得注意的是,滞后实际上只是几乎不明显。但是,对于像小行星这样小的游戏来说,真的不应该有任何延迟,特别是如果它只以 60 fps 的速度运行。

编辑3:添加了MRE

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

最新更新