Android 2d画布游戏:FPS抖动问题

时间:2010-07-22 19:40:08

标签: android frame-rate

我将我的游戏基于月球着陆器演示,虽然经过大量修改,但我可以达到40-50fps左右,但问题是它在40-50fps之间波动太大,导致移动的图形抖动!它非常烦人,让我的游戏看起来真的很糟糕,实际上它的运行速度很好。

我尝试将线程优先级设置得更高,但这让它变得更糟......现在它会在40-60fps之间波动......

我在考虑将FPS限制在30左右,这样它就会保持不变。这是一个好主意,是否有其他人有经验或不同的解决方案?

谢谢!

这是我的跑圈

@Override
    public void run() {
        while (mRun) {
            Canvas c = null;
            try {
                c = mSurfaceHolder.lockCanvas(null);
                synchronized (mSurfaceHolder) {
                    if(mMode == STATE_RUNNING){

                        updatePhysics();
                    }
                    doDraw(c);
                }
            } finally {
                // do this in a finally so that if an exception is thrown
                // during the above, we don't leave the Surface in an
                // inconsistent state
                if (c != null) {
                    mSurfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
        }

private void updatePhysics() {

        now = android.os.SystemClock.uptimeMillis();

        elapsed = (now - mLastTime) / 1000.0;

        posistionY += elapsed * speed;
        mLastTime = now;
}

5 个答案:

答案 0 :(得分:21)

不要将游戏的逻辑(对象移动等)更新速率基于帧速率。换句话说,将绘图和逻辑更新代码放在两个单独的组件/线程中。这样,您的游戏逻辑就完全独立于您的帧速率。

逻辑更新应基于自上次更新以来经过的时间(让我们称之为delta)。因此,如果您的对象以1px /毫秒移动,那么在每次更新期间,您的对象应该执行以下操作:

public void update(int delta) {
    this.x += this.speed * delta;
}

所以现在即使你的FPS滞后,它也不会影响你物体的移动速度,因为三角洲会变得更大,使物体移动得更远(在某些情况下会出现并发症,但这就是它的要点) )。

这是在逻辑更新对象中计算delta的一种方法(在某个线程循环中运行):

private long lastUpdateTime;
private long currentTime;

public void update() {
    currentTime = System.currentTimeMillis();
    int delta = (int) (currentTime - lastUpdateTime);
    lastUpdateTime = currentTime;
    myGameObject.update(delta); // This would call something like the update method above.
}

希望有所帮助!如果您有任何其他问题,请询问;我自己一直在制作Android游戏。 :)


示例代码:

复制这两个片段(1个活动和1个视图)并运行代码。无论您的FPS是什么,结果都应该是一个白点平滑地落在您的屏幕上。代码看起来有点复杂而且很长,但实际上很简单;评论应该解释一切。

此活动类不太重要。您可以忽略其中的大部分代码。

public class TestActivity extends Activity {

    private TestView view;

    public void onCreate(Bundle savedInstanceState) {
        // These lines just add the view we're using.
        super.onCreate(savedInstanceState);
        setContentView(R.layout.randomimage);
        RelativeLayout rl = (RelativeLayout) findViewById(R.id.relative_layout);
        view = new TestView(this);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                10000, 10000);
        rl.addView(view, params);

        // This starts our view's logic thread
        view.startMyLogicThread();
    }

    public void onPause() {
        super.onPause();
        // When our activity pauses, we want our view to stop updating its logic.
        // This prevents your application from running in the background, which eats up the battery.
        view.setActive(false);
    }
}

这堂课是令人兴奋的东西!

public class TestView extends View {

    // Of course, this stuff should be in its own object, but just for this example..
    private float position; // Where our dot is
    private float velocity; // How fast the dot's moving

    private Paint p; // Used during onDraw()
    private boolean active; // If our logic is still active

    public TestView(Context context) {
        super(context);
        // Set some initial arbitrary values
        position = 10f;
        velocity = .05f;
        p = new Paint();
        p.setColor(Color.WHITE);
        active = true;
    }

    // We draw everything here. This is by default in its own thread (the UI thread).
    // Let's just call this thread THREAD_A.
    public void onDraw(Canvas c) {
        c.drawCircle(150, position, 1, p);
    }

    // This just updates our position based on a delta that's given.
    public void update(int delta) {
        position += delta * velocity;
        postInvalidate(); // Tells our view to redraw itself, since our position changed.
    }

    // The important part!
    // This starts another thread (let's call this THREAD_B). THREAD_B will run completely
    // independent from THREAD_A (above); therefore, FPS changes will not affect how
    // our velocity increases our position.
    public void startMyLogicThread() {
        new Thread() {
            public void run() {
                // Store the current time values.
                long time1 = System.currentTimeMillis();
                long time2;

                // Once active is false, this loop (and thread) terminates.
                while (active) {
                    try {
                        // This is your target delta. 25ms = 40fps
                        Thread.sleep(25);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }

                    time2 = System.currentTimeMillis(); // Get current time
                    int delta = (int) (time2 - time1); // Calculate how long it's been since last update
                    update(delta); // Call update with our delta
                    time1 = time2; // Update our time variables.
                }
            }
        }.start(); // Start THREAD_B
    }

    // Method that's called by the activity
    public void setActive(boolean active) {
        this.active = active;
    }
}

答案 1 :(得分:3)

我在想,上面的一些代码可能会出现问题,而不是效率低下。我在谈论这段代码......

   // The important part!
// This starts another thread (let's call this THREAD_B). THREAD_B will run completely
// independent from THREAD_A (above); therefore, FPS changes will not affect how
// our velocity increases our position.
public void startMyLogicThread() {
    new Thread() {
        public void run() {
            // Store the current time values.
            long time1 = System.currentTimeMillis();
            long time2;

            // Once active is false, this loop (and thread) terminates.
            while (active) {
                try {
                    // This is your target delta. 25ms = 40fps
                    Thread.sleep(25);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }

                time2 = System.currentTimeMillis(); // Get current time
                int delta = (int) (time2 - time1); // Calculate how long it's been since last update
                update(delta); // Call update with our delta
                time1 = time2; // Update our time variables.
            }
        }
    }.start(); // Start THREAD_B
}

具体来说,我正在考虑以下几行......

// This is your target delta. 25ms = 40fps
Thread.sleep(25);

在我看来,只是让线程无所事事是浪费宝贵的处理时间,而事实上你想要做的就是执行更新,然后,如果更新花费的时间少于25毫米,然后睡觉线程的更新期间使用的和25毫米(或任何你选择的帧速率)的差异。以这种方式,更新将在呈现当前帧时发生,并且将完成以使下一帧更新使用更新的值。

我能想到的唯一问题是需要进行某种同步,以便当前帧渲染不使用部分更新的值。也许更新到您的一组值的新实例,然后在渲染之前使新实例成为当前实例。

我想我记得在图形书中读到一些关于目标是尽可能多地执行更新同时保持在所需帧速率内的东西,然后,只有它们执行屏幕更新。

这当然需要一个线程来驱动更新 - 如果你使用SurfaceView,当你锁定画布时,渲染是由这个线程控制的(理论上,根据我的理解)。

所以,在代码中,它更像是......

// Calculate next render time
nextRender = System.currentTimeInMillis() + 25;

while (System.currentTimeInMillis() < nextRender)
{
    // All objects must be updated here
    update();

    // I could see maintaining a pointer to the next object to be updated,
    // such that you update as many objects as you can before the next render, and 
    // then continue the update from where you left off in the next render...
}

// Perform a render (if using a surface view)
c = lockCanvas() blah, blah...
// Paint and unlock

// If using a standard view
postInvalidate();

祝你好运,任何使用此功能的人的反馈肯定会帮助我们所有人学习......

rpbarbati

答案 2 :(得分:0)

我认为这是垃圾收集器

答案 3 :(得分:0)

如果您的游戏行动很重,我会使用SurfaceView而不是View。如果您不需要快速更新GUI,那么View就可以了,但对于2D游戏,使用SurfaceView总是更好。

答案 4 :(得分:0)

我有类似的问题,抖动使大对象移动看起来不均匀。尽管“速度”是相同的,但是不同长度的步骤使得运动看起来很跳跃。 Broody - 你说SurfaceView更好,但是,在Android 3.0之后这不是真的,因为View是HW加速但是.lockCanvas返回的画布不是。 史蒂文 - 是的,这可能会引起问题,但很容易被发现。 /雅各布