Android 2d画布游戏:FPS抖动问题
我的游戏基于月球着陆器演示版,虽然经过了大量修改,但我可以获得大约40-50fps的速度,但问题是它在40-50fps之间波动太大,导致移动图形抖动!这很烦人,让我的游戏看起来真的很糟糕,而事实上它运行在一个很好的帧速率 我试着将线程优先级设置得更高,但这只会让事情变得更糟。。。现在它将在40-60帧之间波动 我在考虑将FPS限制在30左右,这样它就会保持不变。这是一个好主意吗?其他人是否有经验或其他解决方案 谢谢 这是我的跑步循环Android 2d画布游戏:FPS抖动问题,android,frame-rate,Android,Frame Rate,我的游戏基于月球着陆器演示版,虽然经过了大量修改,但我可以获得大约40-50fps的速度,但问题是它在40-50fps之间波动太大,导致移动图形抖动!这很烦人,让我的游戏看起来真的很糟糕,而事实上它运行在一个很好的帧速率 我试着将线程优先级设置得更高,但这只会让事情变得更糟。。。现在它将在40-60帧之间波动 我在考虑将FPS限制在30左右,这样它就会保持不变。这是一个好主意吗?其他人是否有经验或其他解决方案 谢谢 这是我的跑步循环 @Override public void run()
@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;
}
不要将游戏的逻辑(对象移动等)更新速率建立在帧速率的基础上。换句话说,将图形和逻辑更新代码放在两个单独的组件/线程中。这样,您的游戏逻辑完全独立于您的帧率 逻辑更新应该基于自上次更新以来经过的时间(我们称之为
delta
)。因此,如果对象以1px/毫秒的速度移动,则在每次更新期间,对象应执行以下操作:
public void update(int delta) {
this.x += this.speed * delta;
}
因此,即使FPS延迟,也不会影响对象的移动速度,因为增量会更大,使对象移动得更远以进行补偿(在某些情况下会有一些复杂情况,但这就是要点)
这是计算逻辑更新对象(在某个线程循环中运行)中的增量的一种方法:
希望有帮助!请询问您是否还有其他问题;我一直在自己制作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;
}
}
我想这是关于垃圾收集器的如果你的游戏是动作密集型的,我会使用SurfaceView而不是View。如果您不需要快速更新GUI,那么View也可以,但对于2D游戏,最好使用SurfaceView 我认为上面的一些代码可能不是真的有问题,而是效率低下。我说的是这个代码
// 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();
//计算下一次渲染时间
nextRender=System.currentTimeInMillis()+25;
while(System.currentTimeInMillis()
祝你好运,任何使用它的人的反馈肯定会帮助我们大家学到一些东西
rpbarbati我有一个类似的问题,抖动使大型对象的移动看起来不均匀。即使“速度”相同,不同的步长也会使移动看起来跳跃。 郁闷-你说SurfaceView更好,但是,Android 3.0之后这不是真的,因为视图是硬件加速的,但是返回的画布是.lockCanvas不是。 史蒂文:是的,这很可能会引起问题,但很容易发现。
/Jacob嗨,Andy,我更新了我的问题,加入了游戏循环。这就是你说的实现它的方式吗?我有两个独立的函数,一个用于物理,一个用于绘图。在物理中,我有经过的时间或“delta”正如你在回答中所描述的那样。出于某种原因,我仍然对图形感到不安。我认为正在发生的是,下落的物体并没有以恒定的速度下落,因为delta一直在波动。你确定物理和绘图功能在两个单独的线程中吗?有两个单独的线程将使它们在运行时独立至于抖动,你确定你正确计算了增量(从现在到上次更新之间的时间)?还要确保游戏中的所有物理都取决于该增量,而不是调用了多少次更新。如果您不介意,请发布一些有问题的代码,以便我提供更详细的建议。我将尝试找到一些代码发布。谢谢!它们不在两个单独的thr中
// 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();