去年春天,我在大学学习Android,最后的任务是开发游戏。我不会提供有关游戏本身的任何细节,但我使用了一个将SurfaceView扩展为游戏面板的类,并将游戏循环放在后台线程中。这场比赛需要一个月的努力才能完成,但结果非常好 - 我在Android市场推出它并且现在拥有越来越多的下载量。但是有一个相当大的缺点,它遵循:
要获得此作业中的最高费率,必须制作一个帧率必须保持不变的游戏。因为我获得了最高学位,老师对我的源代码非常满意,我认为没有bugfix。但是 - 问题是帧速率不是恒定的。而且更奇怪的是:在屏幕上移动的图形对象 - CPU运行得越快就越慢。
我有三款不同型号的手机,显示三种不同的速度:
如此简单 - 智能手机在处理器环境中越强大,图形对象在屏幕上移动的速度就越慢 - 人们可能会反过来说 - 对于速度更快的智能手机来说帧率更高。
我在下面提供了背景线程,它包含了游戏循环 - 我无法看到我在这段代码中做错了什么 - 我的老师也没看错
public class MainThread extends Thread {
private static final String TAG = MainThread.class.getSimpleName();
private final static int MAX_FPS = 50; // Antal frames per sekund.
private final static int MAX_FRAME_SKIPS = 5; // Antal förlorade frames.
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
private SurfaceHolder surfaceHolder;
private GamePanel gamePanel;
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
public MainThread(SurfaceHolder surfaceHolder, GamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
@Override
public void run() {
Canvas canvas;
Log.d(TAG, "Starting game loop");
long beginTime; // Cykelns starttid
long timeDiff; // Tiden det tar för en cykel
int sleepTime; // Cykel (update + render = timediff) + sleeptime => frameperiod.
int framesSkipped; // Antalet förlorade frames.
sleepTime = 0;
while (running) {
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
beginTime = System.currentTimeMillis();
framesSkipped = 0;
try {
this.gamePanel.update_graphics();
this.gamePanel.render_graphics(canvas);
}
catch (Exception e) {
System.out.println("fel uppstod i bakgrundstråden: " + e);
}
timeDiff = System.currentTimeMillis() - beginTime;
sleepTime = (int)(FRAME_PERIOD - timeDiff);
//System.out.println("sleeptime: " + sleepTime);
if (sleepTime > 0) {
// Sleeptime ska vara positiv, med marginal. (Annars risk för hackig animation)
try {
Thread.sleep(sleepTime); // Tråden sover en stund.
} catch (InterruptedException e) {}
}
while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
// Används för att "komma igen/komma ikapp".
try {
this.gamePanel.update_graphics(); // endast updatering.
}
catch (Exception e) {
System.out.println("fel uppstod i bakgrundstråden: " + e);
}
sleepTime += FRAME_PERIOD;
framesSkipped++;
}
if (framesSkipped > 0) {
//Log.d(TAG, "Skipped:" + framesSkipped);
}
}
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // slut finally
}
}
}
提前致谢
答案 0 :(得分:1)
我假设您的update_graphics
方法基于FRAME_PERIOD
的固定增量时间。
问题是你的while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS)
追赶循环没有考虑完成追赶模型后剩余的时间。
例如:
您的FRAME_PERIOD
目前是20毫秒。假设Galaxy Mini运行update_graphics
需要2ms,运行render_graphics
需要19ms。现在它将使用sleepTime=-1ms
进入追赶循环,因此它将运行update_graphics
一次。然后在下一次while循环继续时,它会在try块中再次调用update_graphics
和render_graphics
。当render_graphics
完成第二次绘制时,自上次完成以来已经过了23毫秒,但update_graphics
已被调用两次。因此,似乎40ms的游戏时间已经过去了23ms,所以你的游戏似乎运行了40/23或预期速度的173%。
同时,在睡眠时间总是正面的快速设备上,游戏总是显得正确。
另请注意,Galaxy Mini和Galaxy S3之间的速度差异可能会向另一个方向发展(S3显得更快),具体取决于更新和渲染方法所需的相对时间以及它们与{{1}的比率}。
除了上面简化的情况(忽略了Vsync)之外,睡眠时间永远不会被操作系统完全应用 - 每次都会加上或减去几毫秒。
对于没有物理模拟的简单游戏,在更新方法中使用变量增量时间是一种更简单的解决方案。
如果你有一个固定的增量时间(你可能需要使用物理游戏,或者你的游戏已经使用固定增量时间),this site会清楚地说明如何实现它。 / p>