1

我正在编写一个基于 LunarLander 示例项目的小程序。我想要实现的是在像这样的固定间隔之间更新我的SurfaceView(主要重新定位一些s):Drawable

private static final int UPDATE_INTERVAL = 1000 / 20; // 20 FPS

我正在运行一个Thread在它的run()方法中这样做的:

@Override
    public void run() {
        while (isRunning) {
            Canvas c = null;
            try {
                c = view.getSurfaceHolder().lockCanvas(null);
                synchronized (view.getSurfaceHolder()) {
                    if (GameState.RUNNING.equals(state)) {
                        long now = System.currentTimeMillis();
                        if (now - lastUpdate > UPDATE_INTERVAL) {
                            lastUpdate = now;
                            updater.updatePieces();
                        }
                        doDraw(c);
                    }
                }
            } finally {
                if (c != null) {
                    view.getSurfaceHolder().unlockCanvasAndPost(c);
                }
            }
        }
    }

我的问题是这可能Thread正忙于等待(now - lastUpdate > UPDATE_INTERVAL)条件成为true。但是如果重绘需要超过 1000 / 20 秒,还有另一种情况。这意味着动画并不总是无缝的。我在我的模拟器中试了一下,通常没问题,但有时我Drawable的速度变慢了,然后它加速了大约半秒。这只是模拟器还是其他?

我不会在细节上打扰你,updatePieces()只是从上到下移动一个小圆圈。我没有创建任何新对象。我不知何故觉得这段代码不正确。这是动画Drawables 的好习惯还是我遗漏了一些明显的东西?我对这个话题有点陌生。

我将触摸和按键事件从 UI 线程传播到这个线程,所以我真的不想调用sleep()它,Thread因为它可能会导致 UI 线程变得无响应。

4

2 回答 2

1

想想物体在现实中的反应。物体移动一定距离。它在一定时间内移动的距离取决于速度。速度取决于最后的加速度。距离、速度和加速度都取决于时间。

在您的代码中,您根本不处理时间因此对您当前的代码没有现实世界的解释。像这样把你的物理放在一起:

  1. 实现一个增量系统,比如欧拉积分。该系统应包含有关绘制前一帧所用时间的信息。注意:欧拉积分在加速度恒定(如重力)时是准确的,而不是在加速度变化时准确。但这更多是一个优化主题,现在不要考虑这个问题。
  2. 当您知道前一帧花了多长时间时,您也知道您的对象根本没有移动的时间。
  3. 在下一次更新中考虑那个时间。

例如:

public void updatePieces(float timeDelta) {
    // The amount of an acceleration depends on a force
    // in a specified direction. You can skip this, and just
    // give your object a velocity. But to consider acceleration
    // later on, you have to assign forces and resolve them in
    // their respective direction.
    mAccelerationX  = fResX / weight;             
    mAccelerationY  = fResY / weight; 

    // Consider the previous velocity and add the change
    // in velocity.
    mVelocityX = mVelocityX + (mAccelerationX * timeDelta);
    mVelocityY = mVelocityY + (mAccelerationY * timeDelta);

    mPositionX = mPositionX + (mVelocityX * timeDelta);
    mPositionY = mPositionY + (mVelocityY * timeDelta);
}

了解一点物理学有助于理解为什么这是有道理的:

mAccelerationX  = fResX / weight;

基本上是牛顿第二定律F = ma

mVelocityX = mVelocityX + (mAccelerationX * timeDelta);

有效,因为我们正在做的是将加速度乘以m/s^2时间s。这将为您提供自上一帧以来速度发生了多少变化。简单的取消给了我们(m/s^2)*s = m*s/s^2 = m/s。添加之前的速度并完成。

同样的逻辑适用于:

mPositionX = mPositionX + (mVelocityX * timeDelta);
// (m/s)*s  = m*s/s = m

请注意,您可能必须添加一个比例因子来相乘,以使物理效果在您的屏幕上逼真 - 例如多少像素等于五米,或类似的东西。

有大量文章对此进行了描述,例如 Glenn Fiedler 的Integration Basics。搜索,你会发现更多。

于 2012-08-15T12:09:17.187 回答
0

对于任何有其他工作要做的系统,这不是一个基于时间做任何事情的好习惯:您的代码正在消耗尽可能多的 CPU 时间。您编码的内容称为“忙循环”。这意味着当您没有工作要做时,您仍然很忙。

更好的选择包括使用某种类型的计时器/警报(java Timer、Android AlarmManager)来安排在其时间完成的工作,或者调用 Thread.sleep() 以延迟到您的下一个更新间隔。

于 2012-08-15T11:12:33.777 回答