平滑 Android 游戏循环
Posted
技术标签:
【中文标题】平滑 Android 游戏循环【英文标题】:Smoothing out Android Game Loop 【发布时间】:2014-07-03 19:11:25 【问题描述】:我将下面的代码用作简单 android 游戏的游戏循环。它工作得很好,但游戏中时不时会出现一些小问题。一切都冻结了一秒钟,然后向前跳得比应有的更远。我对游戏循环进行了大量阅读,但在实现平滑循环时仍然遇到了一些麻烦。插值会解决这个问题吗?我怎样才能平滑运动?
private final static int MAX_FPS = 30;
private final static int MAX_FRAME_SKIPS = 5;
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
protected Void doInBackground(Void... params)
Log.d("MainTask", "starting game task");
Canvas canvas;
long begin; // the time when the cycle began
long delta; // the time it took for the cycle to execute
int sleep; // ms to sleep if ahead
int skipped; // number of frames being skipped
sleep = 0;
while (running)
canvas = null;
try
canvas = surfaceHolder.lockCanvas();
synchronized (surfaceHolder)
begin = System.currentTimeMillis();
skipped = 0;
gamePanel.update();
gamePanel.draw(canvas);
delta = System.currentTimeMillis() - begin;
sleep = (int)(FRAME_PERIOD - delta);
if (sleep > 0)
try
Thread.sleep(sleep);
catch(InterruptedException e)
while (sleep < 0 && skipped < MAX_FRAME_SKIPS)
gamePanel.update();
sleep += FRAME_PERIOD;
skipped++;
finally
if (canvas != null) surfaceHolder.unlockCanvasAndPost(canvas);
Log.d("MainTask", "the task is complete");
return null;
【问题讨论】:
见:source.android.com/devices/graphics/architecture.html#loops 【参考方案1】:您实际上已经为这个循环正确地完成了大多数事情。但是,我可以看到一些潜在的陷阱
您没有将帧时间传递给您的更新方法。这意味着您的更新方法必须假定某个帧时间。这可能是你跳跃的原因。如果您接近机器的最大性能,您将不会获得恒定的帧速率,您就是不会。这大体上无关紧要。如果你错过了一个框架,你的大脑会填补丢失的框架,它看起来很好。绝对重要的是可变对象speed。在这种情况下,可变帧速率将导致对象的速度可变。您的更新方法应该是:
long previousFrameTime=System.currentTimeMillis();
while (running)
.....
.....
long currentTime=System.currentTimeMillis();
long frameTime=currentTime-previousFrameTime
previousFrameTime=currentTime
gamePanel.update(frameTime/1000.0); //I personally like my frametimes in seconds, hence /1000.0, but thats up to you
.....
.....
在确定移动多远时,更新中的所有移动都应考虑frametime
要说明这很重要,请参阅以下两个图表。一个具有假定的帧速率,另一个具有测量的帧速率。点代表实际渲染的帧,线条代表观众大脑中发生的插值。
您持有 Canvas 锁的时间也比 nessissary 长得多。一直到你的更新方法和睡眠,你在画布上都有一个锁。 This saps performance.在开始渲染前获取锁,完成后立即释放
gamePanel.update();
canvas = surfaceHolder.lockCanvas();
gamePanel.draw(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
【讨论】:
即使按住画布锁不会导致打嗝,它绝对值得修复 好吧,Canvas 锁定部分是有意义的。帧时间是指我的增量吗?我将如何使用它?我还需要在内部while循环中重新计算吗?还是继续使用上面找到的增量? @user2694189 您的增量似乎是帧的处理时间(忽略任何睡眠) - 这通常不是增量,通常增量是帧时间。您想在进行更新之前记录时间,然后在更新之前记录下一帧,您将再次获得时间,并获得两者之间的差异。这是帧时间。在更新方法内部,一切都会有速度;你将 frameTime 乘以速度,得到它应该移动那个帧的距离 这同样适用于任何加速度,加速度乘以帧时间以获得速度的变化。这称为欧拉积分。那里有更好的集成,购买 euler 通常足以满足游戏循环,并且是迄今为止最简单的。在理想情况下,frameTime 将等于 FRAME_PERIOD,但 FRAME_PERIOD 是目标,而我的 frameTime 是您获得的真实时间(您可能希望将 FRAME_PERIOD 重命名为 TARGET_FRAME_PERIOD 举个例子。想象一个速度为 1m/s 的物体。然后我们以 0.1 秒的目标 FRAME_PERIOD 进行渲染。在当前代码下(使用变量 frameRate),我们可能会得到以下结果(时间、位置); (0,0) (0.1,0.1), (0.15,0.2), (0.28,0.3)。您可以看到所有这些数据点不仅具有可变帧速率。立场不对。考虑到帧时间,您会得到 (0,0), (0.15,0.15), (0.28,0.28)。帧速率仍然错误,但对象至少在正确的位置【参考方案2】:这可能有点太晚了,但这是我实现游戏循环的方式。有了这个,它可以帮助您跟踪游戏的滴答声(或更新)和每秒帧数。
long lastTime = System.nanoTime();
double nsPerTick = 1_000_000_000D/60D;
int frames = 0;
int ticks = 0;
long lastTimer = System.currentTimeMillis();
double delta = 0;
while (mRunning)
Canvas canvas = null;
long now = System.nanoTime();
delta += (now-lastTime)/nsPerTick;
lastTime = now;
boolean shouldRender = false;
while (delta >= 1)
ticks++;
gameView.update();
delta -= 1;
shouldRender = true;
if (shouldRender)
frames++;
canvas = surfaceHolder.lockCanvas();
gameView.render(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
if (System.currentTimeMillis() - lastTimer >= 1000)
lastTimer += 1000;
Log.d("FPS", "Ticks: " + ticks + ", Frames: " + frames);
ticks = 0;
frames = 0;
这是基于 vanZeben 的视频教程,旨在在 PC 上制作 Java 游戏,但它也适用于 Android 游戏(至少对我而言)。
希望对你有帮助。
参考:https://www.youtube.com/watch?v=VE7ezYCTPe4&list=PL8CAB66181A502179
【讨论】:
以上是关于平滑 Android 游戏循环的主要内容,如果未能解决你的问题,请参考以下文章
使用 OpengL Surface 为 Android 制作游戏循环的最佳方法
使用 SurfaceView 的 Android 游戏循环;快速更改相机“镜头”会导致黑色闪光等