安卓平滑游戏循环

19


我在OpenGL中遇到了平滑滚动的问题(在SGS2和ACE上进行测试)
我创建了简单的应用程序 - 只有图像的固定速度水平滚动,或者只有一个图像(玩家)通过加速器移动, 但它的运动不平滑 :-(
我尝试了许多不同的代码,但没有满意的结果...

首先,我尝试使用GLSurfaceView.RENDERMODE_CONTINUOUSLY来处理,并将所有代码放在onDrawFrame中:

    public void onDrawFrame(GL10 gl) 
    {
      updateGame(gl, 0.017f);
      drawGame(gl); 
    }

这是最简单和绝对流畅的方式!! - 但它依赖于硬件速度 (= 无用)


    public void onDrawFrame(GL10 gl) 
    { 
      frameTime = SystemClock.elapsedRealtime();        
      elapsedTime = (frameTime - lastTime) / 1000; 
      updateGame(gl, elapsedTime);
      drawGame(gl); 
      lastTime = frameTime; 
    }

这是最好的选择,但不如之前那个流畅,有时会出现闪烁。


我尝试了GLSurfaceView.RENDERMODE_WHEN_DIRTY,在onDrawFrame中只绘制对象,并在单独的线程中使用此代码:

   while (true) 
   {
     updateGame(renderer, 0.017f);
     mGLSurfaceView.requestRender();
     next_game_tick += SKIP_TICKS;
     sleep_time = next_game_tick - System.currentTimeMillis();
     if (sleep_time >= 0) 
     {
       try 
       {
          Thread.sleep(sleep_time);
       } 
       catch (Exception e) {}
       }
       else 
       {
         Log.d("running behind: ", String.valueOf(sleep_time));
       }
     } 

这不是流畅的问题,也不是“跟不上”的问题。


我的目标是像上面第一个代码示例中一样平滑地移动图像。

可能的错误出现在我没有注意到的其他地方。请问有人可以帮帮我吗?
RENDERMODE_WHEN_DIRTY和RENDERMODE_CONTINUOUSLY哪个更好用?

谢谢。


1
通过加速器移动。加速计会给你返回跳动的数据。这可能是你的问题吗?尝试通过执行类似于“someLoc += (newLoc - someLoc) * .1f;”这样的操作来平滑移动。 - zezba9000
你好,如果我尝试禁用加速度计,并仅通过恒定值移动图像,它也会偶尔闪烁(现在正在尝试使用RENDERMODE_WHEN_DIRTY)。 - Commanche
1个回答

34

我和同样的问题纠缠了好几天。循环在没有任何安卓时间参考的情况下看起来很平滑,但一旦包含任何类型的“时间同步”,安卓开发控制之外的外部因素会给最终结果带来严重的不连续性。

基本上,这些因素包括:

  • 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和真实时间时钟产生的位移量跨越几帧)。

    // avoid GC in your threads. declare nonprimitive variables out of onDraw
    float smoothedDeltaRealTime_ms=17.5f; // initial value, Optionally you can save the new computed value (will change with each hardware) in Preferences to optimize the first drawing frames 
    float movAverageDeltaTime_ms=smoothedDeltaRealTime_ms; // mov Average start with default value
    long lastRealTimeMeasurement_ms; // temporal storage for last time measurement

    // smooth constant elements to play with
    static final float movAveragePeriod=40; // #frames involved in average calc (suggested values 5-100)
    static final float smoothFactor=0.1f; // adjusting ratio (suggested values 0.01-0.5)

    // sample with opengl. Works with canvas drawing: public void OnDraw(Canvas c)   
    public void onDrawFrame(GL10 gl){       
        updateGame(gl, smoothedDeltaRealTime_ms); // divide 1000 if your UpdateGame routine is waiting seconds instead mili-seconds.
        drawGame(gl);  

        // Moving average calc
        long currTimePick_ms=SystemClock.uptimeMillis();
        float realTimeElapsed_ms;
        if (lastRealTimeMeasurement_ms>0){
        realTimeElapsed_ms=(currTimePick_ms - lastRealTimeMeasurement_ms);
        } else {
                 realTimeElapsed_ms=smoothedDeltaRealTime_ms; // just the first time
        }
        movAverageDeltaTime_ms=(realTimeElapsed_ms + movAverageDeltaTime_ms*(movAveragePeriod-1))/movAveragePeriod;

         // Calc a better aproximation for smooth stepTime
        smoothedDeltaRealTime_ms=smoothedDeltaRealTime_ms +(movAverageDeltaTime_ms - smoothedDeltaRealTime_ms)* smoothFactor;

        lastRealTimeMeasurement_ms=currTimePick_ms;
    }

    // Optional: check if the smoothedDeltaRealTIme_ms is too different from original and save it in Permanent preferences for further use.

对于固定时间步长方案,可以实现一个中间的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

更详细的信息如下: enter image description here

如何在使用Android的GLSurfaceView.RENDERMODE_CONTINUOUSLY时限制帧率?

System.currentTimeMillis vs System.nanoTime

方法System.currentTimeMillis()是否真的返回当前时间?


再次问候, 所以,这个例子中一定有错误 - smoothTimeStep的值被反复增加,但是如果我尝试“smoothTimeStep = gameTimeElapsed * smoothFactor +(temp-gameTimeElapsed)”- 看起来很好:-)现在好多了,但有时会闪烁(不太频繁)- 我记录了smoothTimeStep,这里是它的值:0.021 0.021 0.021 ........ 0.06 0.095 0.03 0.008 0.007 0.005 0.021 0.021 也许这只是三星硬件的问题(在SGS2上测试过),我将在另一台设备上尝试。 - Commanche
你是对的。在手动简化和适应我的代码时,我输入了错误的公式,正如你引用的那样。(现已修复并在真实设备上测试)。我的情况需要与背景音乐同步(音频和动画同步运行)。我编辑了答案以更好地满足您的要求,而不考虑音乐实时要求。(移动平均值)。尝试这段代码,如果您感到满意,我建议将标题编辑为更一般的问题,例如“Android平滑游戏循环”。该问题(您的问题)和解决方案与绘图技术(canvas或openGL)无关。 - javqui
你认为使用Android Timer类怎么样?我一直在使用它,动画效果很不错。 - Ogen
@javqui,你好,我已经很长时间以来一直在为我的Android Java游戏循环遇到的问题而苦恼,如果你有时间的话,能否帮我看一下我的开放性问题?谢谢:https://dev59.com/kFkT5IYBdhLWcg3wfvmo?noredirect=1#comment64502253_38512936 - Zippy
3
刚刚发现这个解决方案,它的效果非常好。太棒了! - AudioGuy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接