我和同样的问题纠缠了好几天。循环在没有任何安卓时间参考的情况下看起来很平滑,但一旦包含任何类型的“时间同步”,安卓开发控制之外的外部因素会给最终结果带来严重的不连续性。
基本上,这些因素包括:
- Android中未实现eglSwapInterval,因此很难知道硬件何时在屏幕上公布最终图像(硬件屏幕同步)
- Thread.sleep不精确。线程可能睡得比要求的时间长或短。
- SystemClock.uptimeMillis()、System.nanoTime()、System.currentTimeMillis()等计时相关的测量并不准确(它们是精确的)。
这个问题与绘图技术(绘图、OpenGL 1.0/1.1和2.0)以及游戏循环方法(固定时间步、插值、可变时间步)无关。
像你一样,我也尝试过Thread.sleep、疯狂插值、定时器等方法。不管你做什么,我们都无法控制这些因素。
根据该网站上许多问答的基本规则,生成平稳连续动画的基本规则是:
- 通过删除所有动态内存请求,将GC最小化。
- 尽快地渲染帧(在大多数安卓设备上,40至60fps是可以的)。
- 使用固定时间步长和插值或可变时间步长。
- 优化更新物理和绘制例程以相对恒定时间执行,而不带有高峰变化。
当然,在发布这个问题之前,你做了很多前期工作,通过优化updateGame()和drawGame()(没有明显的GC和相对恒定的执行时间),以获得主循环中的平稳动画,正如你所提到的:“简单而绝对平滑”。
针对你特殊的情况——具有可变步长时间且没有与实时事件(例如音乐)完美同步的特殊要求,解决方案很简单:“平滑步长时间变量”。
该解决方案适用于其他游戏循环方案(带有可变渲染的固定时间步长),并且易于传输概念(平滑由updateGame和真实时间时钟产生的位移量跨越几帧)。
float smoothedDeltaRealTime_ms=17.5f;
float movAverageDeltaTime_ms=smoothedDeltaRealTime_ms;
long lastRealTimeMeasurement_ms;
static final float movAveragePeriod=40;
static final float smoothFactor=0.1f;
public void onDrawFrame(GL10 gl){
updateGame(gl, smoothedDeltaRealTime_ms);
drawGame(gl);
long currTimePick_ms=SystemClock.uptimeMillis();
float realTimeElapsed_ms;
if (lastRealTimeMeasurement_ms>0){
realTimeElapsed_ms=(currTimePick_ms - lastRealTimeMeasurement_ms);
} else {
realTimeElapsed_ms=smoothedDeltaRealTime_ms;
}
movAverageDeltaTime_ms=(realTimeElapsed_ms + movAverageDeltaTime_ms*(movAveragePeriod-1))/movAveragePeriod;
smoothedDeltaRealTime_ms=smoothedDeltaRealTime_ms +(movAverageDeltaTime_ms - smoothedDeltaRealTime_ms)* smoothFactor;
lastRealTimeMeasurement_ms=currTimePick_ms;
}
对于固定时间步长方案,可以实现一个中间的updateGame来改善结果:
float totalVirtualRealTime_ms=0;
float speedAdjustments_ms=0; // to introduce a virtual Time for the animation (reduce or increase animation speed)
float totalAnimationTime_ms=0;
float fixedStepAnimation_ms=20; // 20ms for a 50FPS descriptive animation
int currVirtualAnimationFrame=0; // useful if the updateGameFixedStep routine ask for a frame number
private void updateGame(){
totalVirtualRealTime_ms+=smoothedDeltaRealTime_ms + speedAdjustments_ms;
while (totalVirtualRealTime_ms> totalAnimationTime_ms){
totalAnimationTime_ms+=fixedStepAnimation_ms;
currVirtualAnimationFrame++;
// original updateGame with fixed step
updateGameFixedStep(currVirtualAnimationFrame);
}
float interpolationRatio=(totalAnimationTime_ms-totalVirtualRealTime_ms)/fixedStepAnimation_ms;
Interpolation(interpolationRatio);
}
使用canvas和openGlES10测试了以下设备的绘图性能:SG SII(57 FPS),SG Note(57 FPS),SG tab(60 FPS),未经品牌的Android 2.3(43 FPS)在Windows XP上运行缓慢的模拟器(8 FPS)。测试平台沿着指定的真实物理参数路径移动大约45个对象+1个巨大的背景(从70MP源图像纹理),在几个设备之间没有峰值或闪烁(好吧,在模拟器上的8 FPS看起来不太好,但以预期的恒定速度流动)
查看图表以了解Android报告时间的方式。有时Android会报告一个很大的delta时间,只是下一次循环它比平均值小,这意味着对realTime值的读取存在偏移。
![enter image description here](https://istack.dev59.com/vXEmp.webp)
更详细的信息如下:
![enter image description here](https://istack.dev59.com/W5bO4.webp)
如何在使用Android的GLSurfaceView.RENDERMODE_CONTINUOUSLY时限制帧率?
System.currentTimeMillis vs System.nanoTime
方法System.currentTimeMillis()是否真的返回当前时间?