平滑 Android 游戏循环

Dan*_*ore 3 java multithreading android game-loop

我使用下面的代码作为一个简单 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;
}
Run Code Online (Sandbox Code Playgroud)

Ric*_*gle 5

实际上,您已经正确地完成了此循环的大部分操作。不过,我可以看到一些潜在的陷阱

  • 您没有将帧时间传递给更新方法。这意味着您的更新方法必须假定一定的帧时间。这可能是您跳跃的原因。如果您接近机器的最大性能,您将不会获得恒定的帧速率,您就是不会。这基本上并不重要。如果你错过了一帧,你的大脑就会填补缺失的帧,看起来不错。绝对重要的是可变的物体速度。在这种情况下,可变的帧速率将导致对象的速度可变。您的更新方法应该是:

    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
        .....
        .....
     }
    
    Run Code Online (Sandbox Code Playgroud)

    frametime在确定移动距离时,更新中的所有移动都应考虑到这一点

要说明这一点的重要性,请参阅以下两张图。一个具有假定的帧速率,另一个具有测量的帧速率。点代表实际渲染的帧,线代表观看者大脑中发生的插值。

在此输入图像描述

在此输入图像描述

  • 您持有 Canvas 锁的时间也比必要的时间长得多。在整个更新方法和睡眠过程中,您都锁定了画布。这会降低性能。在开始渲染之前获取锁定并在完成后立即释放它

    gamePanel.update();
    canvas = surfaceHolder.lockCanvas();
    gamePanel.draw(canvas);
    surfaceHolder.unlockCanvasAndPost(canvas);
    
    Run Code Online (Sandbox Code Playgroud)

  • 即使握住画布锁不会导致打嗝,但也绝对值得修复 (2认同)