代码之家  ›  专栏  ›  技术社区  ›  Cameron

Android二维画布游戏:FPS抖动问题

  •  5
  • Cameron  · 技术社区  · 15 年前

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

    我试图设置线程优先级更高,但这只是使它更糟。。。现在它将在40-60帧之间波动。。。

    谢谢!

    这是我的跑步循环

    @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 回复  |  直到 15 年前
        1
  •  22
  •   Andy Zhang    15 年前

    不要把游戏的逻辑(物体移动等)更新速度建立在帧速率上。换句话说,将图形和逻辑更新代码放在两个单独的组件/线程中。这样你的游戏逻辑就完全独立于你的帧率。

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

    public void update(int delta) {
        this.x += this.speed * 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.
    }
    


    示例代码:

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

    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;
        }
    }
    
        2
  •  3
  •   Rodney Barbati    13 年前

    我在想,上面的一些代码可能不是真的有问题,而是效率低下。我说的是这个密码。。。

       // 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毫秒(或者你选择的帧速率是多少)。这样,更新将在渲染当前帧时发生,并将完成,以便下一帧更新使用更新的值。

    这里我能想到的唯一问题是,需要进行某种同步,以便当前帧渲染不使用部分更新的值。可能会更新到值集的新实例中,然后在渲染之前将新实例设为当前实例。

    我记得我在一本图形学书中读到过这样一段话:目标是尽可能多地执行更新,同时保持在所需的帧速率范围内,然后,并且只有他们才执行屏幕更新。

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

    // 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();
    

    巴巴蒂

        3
  •  0
  •   Steven Shih    14 年前

        4
  •  0
  •   broody    14 年前

    如果你的游戏是动作密集型的,我会使用SurfaceView而不是View。如果不需要快速更新GUI,那么View就可以了,但是对于2D游戏来说,最好使用SurfaceView。

        5
  •  0
  •   Jacob L    12 年前

    郁闷-你说一个SurfaceView是beter,但是,这不是真的在android3.0之后,因为视图是HW加速的,但是canvas返回的.lockCanvas不是。 史蒂文-是的,这很可能会引起问题,但很容易发现。 /雅各布