我正在尝试使用Java开发2D游戏。到目前为止,我已设法将游戏设置为使用全屏独占模式并在自定义线程中进行主动渲染。我决定使用的游戏循环是固定时间步变量渲染类型。这种类型的游戏循环应该尽可能快地渲染,因为设备可以处理,我并不完全满意。所以我试图使用Thread.sleep()
限制帧速率。
如果我关闭所有渲染,只需在游戏循环中更新游戏,Thread.sleep(1)
就会1 ms
成功睡眠。但是,如果我打开渲染,有时Thread.sleep(1)
的睡眠时间会比1 ms
长,如15 ms
。
我通过添加/删除行来打开/关闭渲染:
BufferedImage drawImage = render(Math.min(1d, lag / TIME_PER_UPDATE));
drawToScreen(drawImage);
导致线程长时间睡眠的原因是什么?
这是我第一次在这些论坛上发帖,所以请告诉我,如果我在帖子中做错了什么,或者这是否重复(我没有找到类似的帖子)。< / p>
import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
public class Main implements KeyListener
{
private static final long serialVersionUID = 1L;
private boolean gameRunning = false;
private final double UPDATE_RATE = 60;
private final double TIME_PER_UPDATE = 1000000000 / UPDATE_RATE;
private final int MAX_UPDATES_BEFORE_RENDERING = 5;
private final int TARGET_FPS = 60;
private int windowWidth;
private int windowHeight;
private GraphicsDevice graphicsDevice;
private DisplayMode defaultDisplayMode;
private Frame frame;
private BufferStrategy bufferStrategy;
private Player player;
public Main()
{
GraphicsDevice[] screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
this.graphicsDevice = screenDevices[0];
// This is later used to restore the original display mode when closing
// the game
defaultDisplayMode = this.graphicsDevice.getDisplayMode();
frame = new Frame("GameTest");
frame.setIgnoreRepaint(true);
frame.setResizable(false);
frame.setUndecorated(true);
// Ensure that the user device supports full screen exclusive mode
if (this.graphicsDevice.isFullScreenSupported())
{
graphicsDevice.setFullScreenWindow(frame);
}
windowWidth = frame.getWidth();
windowHeight = frame.getHeight();
frame.createBufferStrategy(2);
bufferStrategy = frame.getBufferStrategy();
// The frame receives keyboard event dispatched on the EDT-thread.
frame.addKeyListener(this);
initGame();
// Starts the gameThread. The updating of the game state and rendering
GameThread gameThread = new GameThread();
gameThread.start();
}
private void initGame()
{
player = new Player(300, 300);
}
private class GameThread extends Thread
{
@Override
public void run()
{
gameLoop();
}
}
public static void main(String[] Args)
{
new Main();
}
private void gameLoop()
{
gameRunning = true;
double lastStartTime = System.nanoTime();
double startTime;
double elapsedTime = 0;
double lag = 0;
double lastRenderTime;
int updateCount = 0;
while (gameRunning)
{
System.out.println("");
System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
System.out.println("New Gameloop");
System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
startTime = System.nanoTime();
elapsedTime = startTime - lastStartTime;
lag += elapsedTime;
updateCount = 0;
while (lag >= TIME_PER_UPDATE && updateCount < MAX_UPDATES_BEFORE_RENDERING)
{
updateGameState();
lag -= TIME_PER_UPDATE;
updateCount++;
}
if (startTime - lastStartTime > TIME_PER_UPDATE)
{
lastStartTime = startTime - TIME_PER_UPDATE;
}
BufferedImage drawImage = render(Math.min(1d, lag / TIME_PER_UPDATE));
drawToScreen(drawImage);
lastRenderTime = System.nanoTime();
double currentFPS = 1000000000d / (lastRenderTime - startTime);
//Sleeps until target FPS is reached
System.out.println("");
System.out.println("Before sleeping");
System.out.println("");
System.out.println("Current FPS:");
System.out.println(currentFPS);
while (currentFPS > TARGET_FPS && (lastRenderTime - startTime) < TIME_PER_UPDATE)
{
//Lets the CPU rest
Thread.yield();
double beginSleepTime = System.nanoTime();
try
{
Thread.sleep(1);
} catch (Exception e)
{
e.printStackTrace();
}
double endSleepTime = System.nanoTime();
lastRenderTime = System.nanoTime();
currentFPS = 1000000000d / (lastRenderTime - startTime);
System.out.println("");
System.out.println("--------------------------------");
System.out.println("Sleeping");
System.out.println("");
System.out.println("Time slept in ms:");
System.out.println("");
System.out.println((endSleepTime - beginSleepTime) / 1000000d);
System.out.println("");
System.out.println("current FPS");
System.out.println("");
System.out.println(currentFPS);
}
lastStartTime = startTime;
}
}
private void updateGameState()
{
player.update();
}
private void drawToScreen(BufferedImage drawImage)
{
try
{
Graphics2D g2d = (Graphics2D) bufferStrategy.getDrawGraphics();
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.clearRect(0, 0, windowWidth, windowHeight);
g2d.setBackground(Color.BLACK);
g2d.drawImage(drawImage, 0, 0, windowWidth, windowHeight, null);
g2d.dispose();
if (!bufferStrategy.contentsLost())
{
bufferStrategy.show();
}
} catch (Exception e)
{
e.printStackTrace();
}
}
private BufferedImage render(double delta)
{
BufferedImage drawImage = new BufferedImage(windowWidth, windowHeight, BufferedImage.TYPE_INT_ARGB);
drawImage.createGraphics();
Graphics2D g = (Graphics2D) drawImage.getGraphics();
g.setBackground(Color.WHITE);
g.clearRect(0, 0, windowWidth, windowHeight);
//Render player
g.setColor(Color.BLUE);
g.fillRect((int) Math.round(player.getLocX() + delta * player.getSpeedX()), (int) Math.round(player.getLocY() + delta * player.getSpeedY()), 64, 64);
g.dispose();
return drawImage;
}
@Override
public void keyPressed(KeyEvent keyEvent)
{
switch (keyEvent.getKeyCode())
{
case KeyEvent.VK_ESCAPE:
graphicsDevice.setDisplayMode(defaultDisplayMode);
System.exit(0);
break;
case KeyEvent.VK_A:
player.setSpeedX(-player.getMoveSpeed());
break;
case KeyEvent.VK_D:
player.setSpeedX(player.getMoveSpeed());
break;
case KeyEvent.VK_W:
player.setSpeedY(-player.getMoveSpeed());
break;
case KeyEvent.VK_S:
player.setSpeedY(player.getMoveSpeed());
break;
case KeyEvent.VK_SPACE:
break;
case KeyEvent.VK_LESS:
break;
case KeyEvent.VK_I:
break;
}
}
@Override
public void keyReleased(KeyEvent keyEvent)
{
switch (keyEvent.getKeyCode())
{
case KeyEvent.VK_A:
player.setSpeedX(0);
break;
case KeyEvent.VK_D:
player.setSpeedX(0);
break;
case KeyEvent.VK_W:
player.setSpeedY(0);
break;
case KeyEvent.VK_S:
player.setSpeedY(0);
break;
case KeyEvent.VK_SPACE:
break;
case KeyEvent.VK_LESS:
break;
case KeyEvent.VK_I:
break;
}
}
@Override
public void keyTyped(KeyEvent keyEvent)
{
}
private class Player
{
protected double speedX;
protected double speedY;
protected double locX;
protected double locY;
protected double moveSpeed;
public Player(int locX, int locY)
{
speedX = 0;
speedY = 0;
this.locX = locX;
this.locY = locY;
moveSpeed = 3d;
}
public void update()
{
locY += speedY;
locX += speedX;
}
public void setSpeedX(double speedX)
{
this.speedX = speedX;
}
public void setSpeedY(double speedY)
{
this.speedY = speedY;
}
public double getSpeedX()
{
return speedX;
}
public double getSpeedY()
{
return speedY;
}
public double getLocX()
{
return locX;
}
public double getLocY()
{
return locY;
}
public double getMoveSpeed()
{
return moveSpeed;
}
}
}
答案 0 :(得分:6)
java中的sleep()
方法将当前正在执行的线程(处于运行状态)休眠1 ms。
1 ms后线程进入可运行状态(能够运行),现在它取决于调度程序何时从可运行状态获取线程并执行它(即运行状态)。
出于这个原因,你可以假设线程在再次运行之前至少休眠1ms。
下图描述了不同的线程状态:
答案 1 :(得分:0)
java docs明确说明(对于thread.sleep())
使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。
这是系统内的调度和系统时序的摆布。保证时间的唯一方法是确保线程阻止其执行并阻止循环,但这会在其他地方产生问题。
为了它的价值,我发现在一定时间内睡眠线程通常是不好的,除非有绝对的理由这样做,事情应该由不是固定时间间隔的其他事件触发。
答案 2 :(得分:0)
根据javadoc:
Thread.sleep()
导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统定时器和调度程序的精度和准确性。
因此,Thread.sleep(ms)
的准确度较低。
其次,请注意此方法会抛出已检查的异常ThreadInterruptedException
。哪个可以由spurious wakeup触发。因此即使Thread.sleep(1000)
也可以在ms之后完成。
更高精度的替代解决方案是LockSupport.parkNanos()
。但是使用这种方法你应该注意其他线程的中断。 PS:还有一个Thread.sleep(ms,nanos)
与Thread.sleep(ms)
具有相同的低精度(毫秒秒只是舍入到ms)。