游戏循环 - 元素越长它们就越快

时间:2013-08-29 15:14:25

标签: java multithreading interpolation game-loop

在我的游戏中,我有多个主题。一个线程将信号发送到计算线程,在此线程接收到信号后,它还会渲染游戏。

主线程看起来像这样,我改编自我见过的其他游戏循环:

while(isRunning) {
    long now = System.nanoTime();
    float elapsed = (now - mStartTime) / 1000000000f;
    mStartTime = now;


    try {
        Log.d("GameThread", "setElapsed = "+elapsed);
        mController.setElapsedTime(elapsed);

        // send signal to logic barrier to start logic-threads
        BaseThread.LogicBarrier.await(); // 1/4

        // logic done!
        BaseThread.AfterLogicBarrier.await(); // 1/4

        // render!
        Log.d("GameThread", "RENDERING! -> localTime="+localTime+", Options.TIMESTEP="+Options.TIMESTEP+", interpol = "+(Options.TIMESTEP / localTime));



        mController.render((localTime / Options.TIMESTEP));

    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }

    // sleep maybe
    diff = System.nanoTime() - mStartTime;
    mController.fireGameDataChange(GameDataListener.FPS, (int) 1000000000/diff);
    if (1000000000 * Options.TIMESTEP - (diff) > 1000000) {
        try {
            Thread.sleep(0, 999999);
        } catch (InterruptedException ex) {
        }
    }
}

对于每个存在的ViewObject,渲染函数如下所示:

mMatrix.reset();
mMatrix.setTranslate(view.getX() - view.getVelocityX()  * interpolation), view.getY() + view.getVelocityY)) * interpolation));
mMatrix.preScale((1.0f * view.getWidth() / mBitmap.getWidth()), (1.0f * view.getHeight() / mBitmap.getHeight()));
mMatrix.postRotate(view.getRotation(), view.getX() + view.getWidth()/2f, view.getY()  + view.getHeight()/2f);
mCanvas.drawBitmap(mBitmap, mMatrix, mBasicPaint);

这就是逻辑线程中发生的事情:

while(isRunning) {
    try {
        BaseThread.LogicBarrier.await(); // 3/4
        int numSubSteps = 0;
        if (Options.SKIP_TICKS != 0) {
            // fixed timestep with interpolation
            localTime += mController.getElapsedTime();
            Log.e("Environment", "localTime="+localTime+" >= "+Options.TIMESTEP+", from elapsed = "+mController.getElapsedTime());
            if (localTime >= Options.TIMESTEP) {
                //numSubSteps = (int) (localTime/Options.TIMESTEP);
                numSubSteps = (int) (localTime/Options.TIMESTEP);
                localTime -= numSubSteps * Options.TIMESTEP;
            }
        }
        Log.e("EnvironmentThread", "localTime="+localTime+", numSub="+numSubSteps);
        if (numSubSteps != 0) {
            // clamp the number of substeps, to prevent simulation grinding spiralling down to a halt
            int clampedSubSteps = (numSubSteps > Options.SKIP_TICKS) ? Options.SKIP_TICKS: numSubSteps;
            for (int i = 0; i < clampedSubSteps; i++) {
                // todo: update game logic -> in time step so no interpolation
                mController.refresh(1);
            }
        }
        BaseThread.AfterLogicBarrier.await(); // 3/4
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
}

同步线程等的过程工作正常,如果我将帧限制为60,我每秒可获得60次刷新并尽可能多地渲染。但是有一个问题,我从昨天起就试图解决 - 没有成功。

查看对象正在加速。他们没有加速度,他们从速度中获得简单的收益。但是它们存在的时间越长,它们就越快。有谁知道为什么会这样?渲染函数不应该干扰视图的位置/速度,因为它只使用getter方法来获取位图,当前位置和速度。

所以我的想法是,这是由于插值,我会觉得有点令人困惑,因为它不是一种反复出现的正弦速度增加,而是在每个视图对象的生命周期中增加。

任何帮助,任何想法,都将非常感激。

这是来自逻辑线程和主线程的一些LogCat输出,显示了插值以及完成所有操作的时间:

08-29 17:09:57.603  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.017999122 >= 0.033333335, from elapsed = 0.017043
08-29 17:09:57.603  26183-26207/com.example.pckg E/EnvironmentThread: numSubSteps = 0.0
08-29 17:09:57.603  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.017999122, numSub=0
08-29 17:09:57.603  26183-26204/com.example.pckg D/GameThread: RENDERING! -> localTime=0.017999122, Options.TIMESTEP=0.033333335, interpol = 1.8519423
08-29 17:09:57.623  26183-26204/com.example.pckg D/GameThread: setElapsed = 0.017807
08-29 17:09:57.623  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.03580612 >= 0.033333335, from elapsed = 0.017807
08-29 17:09:57.623  26183-26207/com.example.pckg E/EnvironmentThread: numSubSteps = 0.0
08-29 17:09:57.623  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.0024727844, numSub=1
08-29 17:09:57.623  26183-26204/com.example.pckg D/GameThread: RENDERING! -> localTime=0.0024727844, Options.TIMESTEP=0.033333335, interpol = 13.480082
08-29 17:09:57.633  26183-26204/com.example.pckg D/GameThread: setElapsed = 0.013305
08-29 17:09:57.633  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.015777785 >= 0.033333335, from elapsed = 0.013305
08-29 17:09:57.633  26183-26207/com.example.pckg E/EnvironmentThread: numSubSteps = 0.0
08-29 17:09:57.633  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.015777785, numSub=0
08-29 17:09:57.633  26183-26204/com.example.pckg D/GameThread: RENDERING! -> localTime=0.015777785, Options.TIMESTEP=0.033333335, interpol = 2.1126752
08-29 17:09:57.653  26183-26204/com.example.pckg D/GameThread: setElapsed = 0.018212
08-29 17:09:57.653  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.033989787 >= 0.033333335, from elapsed = 0.018212
08-29 17:09:57.653  26183-26207/com.example.pckg E/EnvironmentThread: numSubSteps = 0.0
08-29 17:09:57.653  26183-26207/com.example.pckg E/EnvironmentThread: localTime=6.5645203E-4, numSub=1
08-29 17:09:57.653  26183-26204/com.example.pckg D/GameThread: RENDERING! -> localTime=6.5645203E-4, Options.TIMESTEP=0.033333335, interpol = 50.778023
08-29 17:09:57.673  26183-26204/com.example.pckg D/GameThread: setElapsed = 0.01754
08-29 17:09:57.673  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.018196452 >= 0.033333335, from elapsed = 0.01754
08-29 17:09:57.673  26183-26207/com.example.pckg E/EnvironmentThread: numSubSteps = 0.0
08-29 17:09:57.673  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.018196452, numSub=0
08-29 17:09:57.673  26183-26204/com.example.pckg D/GameThread: RENDERING! -> localTime=0.018196452, Options.TIMESTEP=0.033333335, interpol = 1.831859
08-29 17:09:57.683  26183-26204/com.example.pckg D/GameThread: setElapsed = 0.014516
08-29 17:09:57.683  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.032712452 >= 0.033333335, from elapsed = 0.014516
08-29 17:09:57.683  26183-26207/com.example.pckg E/EnvironmentThread: numSubSteps = 0.0
08-29 17:09:57.683  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.032712452, numSub=0
08-29 17:09:57.683  26183-26204/com.example.pckg D/GameThread: RENDERING! -> localTime=0.032712452, Options.TIMESTEP=0.033333335, interpol = 1.01898
08-29 17:09:57.703  26183-26204/com.example.pckg D/GameThread: setElapsed = 0.017108
08-29 17:09:57.703  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.049820453 >= 0.033333335, from elapsed = 0.017108
08-29 17:09:57.703  26183-26207/com.example.pckg E/EnvironmentThread: numSubSteps = 0.0
08-29 17:09:57.703  26183-26207/com.example.pckg E/EnvironmentThread: localTime=0.016487118, numSub=1

不幸的是,我的大脑现在已经落后了,我不明白我现在做错了什么。

编辑:

感谢你推动我的大脑。添加更多调试语句后发现错误。没有数学错误,没有线程错误同步,而是在逻辑部分(refresh()方法)中缺少一个简单的“List.clear()”,这导致列表永远不会被清除,导致同一个对象被移动更常见的是每个计算框架 - 我甚至没有在这里发布,因为我没想到会出现错误。

谢谢你。

3 个答案:

答案 0 :(得分:1)

写了更多的游戏循环,而不是你可以动摇,我觉得你的一切都错了。

你需要两个循环。一个循环就是你的game loop。这步骤将游戏状态向前推进一次。事情发生变化,事情死亡,事物发生碰撞,事情改变方向等等。

第二个循环是你的render loop。这只是采取游戏状态并吐出一个图像。

如果关闭渲染循环,屏幕会变黑,但游戏循环中游戏仍在进行。

如果关闭游戏循环,屏幕不会改变,直到您重新打开游戏循环,因为它会继续呈现相同(未更改)的状态。

现在,看起来你实际上没有游戏循环。你有一个渲染循环试图一次一点地模拟游戏循环,Admiral Ackbar有一两件事(实际上只有一个)可以说。

因此,制作一个完全独立于渲染循环的正确游戏循环,这个问题(以及其他十几个)将会消失。一般来说,添加新功能也会容易得多。

答案 1 :(得分:1)

我建议你看看LWJGL。 Minecraft就是基于此,它可以为您提供一些主题。

通常的“游戏循环”本身涉及一系列步骤,主要包括:

  1. 初始化:首次设置。
  2. 加载:加载要使用的游戏内容
  3. 更新:我认为这就是您所说的游戏逻辑。在这里,元素被更新(移动,碰撞和相似)。
  4. 绘制 :(渲染)
  5. 如果你没有完成GoTo 3
  6. 卸载:通过分配不同的资源(例如纹理,spritesheets,声音效果或bgms)释放您消耗的内存。
  7. 正如您现在所做的那样,您有两个线程要渲染,另一个要更新。如果更新线程变得疯狂,那么你的游戏将呈现无意义的东西,例如加速的精灵。

    以一些现有的框架为例(比如,即将被弃用的XNA游戏工作室,其精神上的继任者MonoGame或PSM),他们在宏观层面处理游戏循环,拥有组件(a例如,Level执行循环中包含的相同步骤。

    有了这个说法,循环本身就“执行”每个元素(特别是 Update Render ),在Level上注入游戏循环对象

答案 2 :(得分:0)

也许这就是即时编译器???它通过将方法编译为本机代码使方法更快。但这种情况不会发生在同一方法的多次调用之前。

- &GT;关闭JIT编译器并检查行为。使用Oracle的Java SE,可以通过添加参数-Djava.compiler=NONE

来完成

查看http://artiomg.blogspot.de/2011/10/just-in-time-compiler-jit-in-hotspot.html