java 安卓流畅游戏循环

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10648325/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 01:58:12  来源:igfitidea点击:

Android smooth game loop

javaandroidopengl-esopengl-es-2.0

提问by Commanche


I have problems with smooth scrolling in OpenGL (testing on SGS2 and ACE)
I created simple applications - only fixed speed horizontal scrolling of images, or only one image (player) moving by accelerator, but it's movement is not smooth :-(
I tried many various of code but no satisfaction...


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

first I tried work with GLSurfaceView.RENDERMODE_CONTINUOUSLYand I put all code to onDrawFrame:

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

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

this is the simplest and absolute smooth!! - but it's dependent on hardware speed (= useless)


这是最简单的绝对流畅!!- 但它取决于硬件速度(= 无用)


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

this is the best of all but it's not as smooth as the previous, sometimes flick


second I tried GLSurfaceView.RENDERMODE_WHEN_DIRTY, in onDrawFrame i have only drawing objects and this code in separate Thread:

这是最好的,但它不像以前那样流畅,有时轻弹


第二我尝试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));
       }
     } 

this is not smooth and it's not problem with "running behind"


My objective is smooth image movement like in first code example above.

Possible error is somewhere else then I looking. Please, Can somebody help me with this?
Is better use RENDERMODE_WHEN_DIRTY or RENDERMODE_CONTINUOUSLY?

这并不顺利,“落后”也不是问题


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

可能的错误是其他地方然后我看。拜托,有人可以帮我吗?
使用 RENDERMODE_WHEN_DIRTY 还是 RENDERMODE_CONTINUOUSLY 更好?

Thank you.

谢谢你。

回答by javqui

I was fighting with the exact same issue for several days. The loop looks smooth without any Android time reference, but as soon it includes any type of “time sync” , external factors out of the android development control introduce serious discontinuities to the final result.

几天来,我一直在与完全相同的问题作斗争。该循环在没有任何 Android 时间参考的情况下看起来很流畅,但是一旦它包含任何类型的“时间同步”,Android 开发控制之外的外部因素就会对最终结果引入严重的不连续性。

Basically, these factors are:

基本上,这些因素是:

  • eglSwapInterval is not implemented in Android, so is difficult to know the moment when the hardware expose the final draw in the screen (hardware screen sync)
  • Thread.sleep is not precise. The Thread may sleep more or less than requested.
  • SystemClock.uptimeMillis()System.nanoTime(), System.currentTimeMillis() and other timing related measurement are not accurate (its precise).
  • eglSwapInterval 在 Android 中没有实现,所以很难知道硬件在屏幕中暴露最终绘制的时刻(硬件屏幕同步)
  • Thread.sleep 不精确。线程可能比请求的睡眠时间更多或更少。
  • SystemClock.uptimeMillis()System.nanoTime(), System.currentTimeMillis() 和其他时间相关的测量不准确(它的精确)。

The issue is independent of the drawing technology (drawing, openGL 1.0/1.1 and 2.0) and the game loop method (fixed time step, interpolation, variable time step). Like you, I was trying Thread.sleep, crazy interpolations, timers, etc. Doesn't matter what you will do, we don't have control over this factors.

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

According with many Q&A on this site, the basic rules to produce smooth continuous animations are:

根据本站的许多问答,产生流畅连续动画的基本规则是:

  • Reduce at the minimum the GC by removing all dynamic memory request.
  • Render frames as fast the hardware can process them (40 to 60fps is ok in most android devices).
  • Use fixed time steps with interpolation or variable time steps.
  • Optimize the update physics and draw routines to be execute in relative constant time without high peaks variance.
  • 通过删除所有动态内存请求,至少减少 GC。
  • 以硬件可以处理的速度渲染帧(在大多数安卓设备中 40 到 60fps 是可以的)。
  • 使用带有插值或可变时间步长的固定时间步长。
  • 优化更新物理和绘制例程,以在相对恒定的时间内执行,没有高峰值方差。

For sure, you made a lot of previous work before post this question by optimizing your updateGame() and drawGame() (without appreciable GC and relative constant execution time) in order to get a smooth animation in your main loop as you mention: “simple and absolute smooth”.

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

Your particular case with variable stepTime and no special requirements to be in perfect sync with realTime events (like music), the solution is simple: “smooth the step Time variable”.
The solution works with other game loop schemes (fixed time step with variable rendering) and is easy to port the concept (smooth the amount of displacement produced by the updateGame and the real time clock across several frames.)

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

For a fixed time step scheme, an intermetiate updateGame can be implemented to improve the results:

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

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);
}

Tested with canvas and openGlES10 drawing with the following devices: SG SII (57 FPS), SG Note(57 FPS) , SG tab(60 FPS), unbranded Android 2.3 (43 FPS) slow emulator running on Windows XP(8 FPS). The test platform draws around 45 objects + 1 huge background (texture from 70MP source image) moving along a path specified in real physics parameters (km/h and G's), without spikes or flick between several devices (well, 8 FPS on the emulator doesn't look good, but its flow at constant speed as expected)

在以下设备上使用 canvas 和 openGlES10 绘图进行测试:SG SII (57 FPS)、SG Note(57 FPS)、SG tab(60 FPS)、在 Windows XP(8 FPS)上运行的无品牌 Android 2.3 (43 FPS) 慢速模拟器。测试平台绘制了大约 45 个对象 + 1 个巨大的背景(来自 70MP 源图像的纹理)沿着真实物理参数(km/h 和 G's)指定的路径移动,在多个设备之间没有尖峰或轻弹(嗯,模拟器上的 8 FPS看起来不太好,但它按预期以恒定速度流动)

Check The graphs for how android report the time. Some times Android report a large delta time and just the next loop it's small than average, meaning an offset on the reading of realTime value.

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

enter image description here

在此处输入图片说明

with more detail: enter image description here

更多细节: 在此处输入图片说明

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

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

System.currentTimeMillis vs System.nanoTime

System.currentTimeMillis 与 System.nanoTime

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

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