游戏循环 - 元素存在的时间越长越快

Posted

技术标签:

【中文标题】游戏循环 - 元素存在的时间越长越快【英文标题】:Game Loop - Elements get faster the longer they exist 【发布时间】:2013-08-29 15:14:24 【问题描述】:

在我的游戏中,我有多个线程。一个线程向计算线程发送信号,该线程收到信号后,也渲染游戏。

主线程看起来像这样,这是我从我见过的其他一些游戏循环中改编而来的:

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()”,导致列表永远不会被清除,从而导致相同的对象被移动更频繁地使用每个计算框架 - 我什至没有在此处发布,因为我没想到会出现错误。

还是谢谢你。

【问题讨论】:

【参考方案1】:

编写的游戏循环数超出了你的想象,我认为你做错了。

你需要两个循环。一个循环是您的game loop。这将游戏状态向前一步迭代。事物移动、事物死亡、事物碰撞、事物改变方向等。

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

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

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

现在,您似乎没有真正的游戏循环。您有一个渲染循环,它尝试一次模拟一点游戏循环,而 Admiral Ackbar 对此有一两件事(实际上只有一件)要说。

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

【讨论】:

你确定吗?调用 render() 的 GameThread.java 只是向其他线程发送信号。我是否应该让线程不同步,只在后台计算它们的回合并拥有自己的时间管理?但是,例如碰撞会被计算得太频繁,或者 - 在更糟糕的情况下 - 太少?另外,我不明白这会如何影响我移动物体的速度...... 移动对象的“速度”来自我似乎也找不到的数学错误,但可能来自自上次渲染以来发生的滴答数的错误计算.至于同步,他们需要同步访问数据,但他们可以有自己的时间管理。我将添加一个同步的小示例。【参考方案2】:

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

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

    初始化:第一次设置。 加载:加载要使用的游戏内容 更新:我认为这就是你所说的游戏逻辑。在这里,元素被更新(运动、碰撞等)。 绘制:(渲染) 如果你还没有完成转到 3 卸载:通过分配各种资源(例如纹理、精灵表、音效或背景音乐)释放您消耗的内存。

正如您现在所做的那样,您有两个线程,一个要渲染,另一个要更新。如果更新线程发疯了,那么您的游戏将渲染无意义的东西,例如加速精灵。

以一些现有框架为例(例如,即将被弃用的 XNA Game Studio,其精神继承者 MonoGame 或 PSM),它们在宏观层面处理游戏循环,具有组件(a Level,例如)执行循环中包含的完全相同的步骤。

话虽如此,循环本身是在每个元素上“执行”的(特别是 UpdateRender),将游戏循环注入到 Level 对象上。

【讨论】:

【参考方案3】:

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

-> 关闭 JIT 编译器并检查其行为。使用 Oracle 的 Java SE 这可以通过添加参数-Djava.compiler=NONE 来完成

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

【讨论】:

方法的执行时间与方法中的逻辑缺陷几乎没有关系...关闭渲染引擎中的JIT似乎是一个非常糟糕的主意。 建议关闭 JIT 战争以确保它对行为没有影响。

以上是关于游戏循环 - 元素存在的时间越长越快的主要内容,如果未能解决你的问题,请参考以下文章

结对编程—贪吃蛇游戏—需求分析

构建和同步多线程游戏循环

使用for循环创建动态DOM元素

如何访问另一个场景中存在的游戏对象[重复]

题解 P1312 Mayan游戏

JavaFX - 游戏循环画布高 CPU 使用率