Android流畅的游戏循环

时间:2012-05-18 07:18:35

标签: java android opengl-es opengl-es-2.0


我在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 个答案:

答案 0 :(得分:33)

几天来,我和同样的问题一直在战斗。循环看起来很平滑而没有任何Android时间参考,但是一旦它包含任何类型的“时间同步”,android开发控件之外的外部因素会给最终结果带来严重的不连续性。

基本上,这些因素是:

  • eglSwapInterval未在Android中实现,因此很难知道硬件在屏幕上显示最终绘图的时刻(硬件屏幕同步)
  • Thread.sleep不准确。线程可能比所请求的更多或更少地休眠。
  • SystemClock.uptimeMillis()System.nanoTime(),System.currentTimeMillis()和其他与时序相关的测量不准确(精确)。

该问题独立于绘图技术(绘图,openGL 1.0 / 1.1和2.0)和游戏循环方法(固定时间步长,插值,可变时间步长)。 和你一样,我正在尝试Thread.sleep,疯狂的插值,计时器等。无论你做什么,我们都无法控制这些因素。

根据本网站上的许多问答,制作流畅连续动画的基本规则是:

  • 通过删除所有动态内存请求,将GC降至最低。
  • 以尽可能快的速度渲染帧,硬件可以处理它们(大多数Android设备都可以使用40到60fps)。
  • 使用带插值或可变时间步长的固定时间步长。
  • 优化更新物理并绘制例程,以相对恒定的时间执行,而不会出现高峰值差异。

当然,你在发布这个问题之前做了很多以前的工作,优化你的updateGame()和drawGame()(没有明显的GC和相对恒定的执行时间),以便在你的主循环中获得平滑的动画提到:“简单而绝对顺畅”。

您的特殊情况与变量stepTime并没有特殊要求与realTime事件(如音乐)完美同步,解决方案很简单:“平滑步骤时间变量”。
该解决方案适用于其他游戏循环方案(具有可变渲染的固定时间步长),并且易于移植概念(平滑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.

对于固定时间步骤方案,可以实施中间更新游戏以改善结果:

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标签(60 FPS),在Windows XP上运行的无品牌Android 2.3(43 FPS)慢速模拟器(8 FPS)。测试平台沿着实际物理参数(km / h和G)中指定的路径移动大约45个对象+ 1个巨大背景(来自70MP源图像的纹理),在几个设备之间没有尖峰或轻弹(好吧,模拟器上的8个FPS)看起来不太好,但它的流速如预期的那样恒定速度。

检查android报告时间的图表。有时候Android会报告一个较大的增量时间,而下一个循环只会比平均值小,这意味着读取实时值的偏移量。

enter image description here

更多细节: enter image description here

How to limit framerate when using Android's GLSurfaceView.RENDERMODE_CONTINUOUSLY?

System.currentTimeMillis vs System.nanoTime

Does the method System.currentTimeMillis() really return the current time?