我刚开始在Android上进行游戏开发,并且正在制作一个超级简单的游戏。
这个游戏基本上类似于“Flappy Bird”。
我设法让所有东西都能运行,但是我遇到了很多卡顿和延迟。
我用来测试的手机是LG G2,因此它应该并且确实可以运行比这更重和复杂的游戏。
基本上有4个“障碍物”,它们彼此距离一个全屏幅度。
当游戏开始时,障碍物开始以恒定的速度移动(朝向角色)。玩家角色的x值在整个游戏中保持不变,而其y值会发生变化。
当角色通过障碍物时(有时甚至在障碍物之后一点),就会出现卡顿。问题在于每次绘制游戏状态时存在不均匀的延迟,导致运动产生卡顿。
- 根据日志,GC没有运行。
- 卡顿不是由于速度过高造成的(我知道这一点,因为在游戏开始时,当障碍物超出视野时,角色的移动非常流畅)
- 我认为这个问题也与FPS无关,因为即使将MAX_FPS字段设置为100,也仍然会出现卡顿。
我的想法是,有一行或多行代码导致某种延迟发生(从而跳过了帧)。 我也认为这些行应该在PlayerCharacter
、Obstacle
和MainGameBoard
的update()
和draw()
方法附近。
问题是,我还是新手,在Android开发和特别是Android游戏开发方面,我不知道可能会导致这种延迟的原因。
我尝试在线寻找答案...不幸的是,我找到的所有答案都指向GC有责任。 但是,由于我不认为它是这种情况(如果我错了,请纠正我),所以这些答案对我没有用。我还阅读了Android开发人员的性能提示
页面,但未找到任何有用的信息。
所以,请帮助我找到解决这些烦人卡顿的答案!
一些代码
MainThread.java:
public class MainThread extends Thread {
public static final String TAG = MainThread.class.getSimpleName();
private final static int MAX_FPS = 60; // desired fps
private final static int MAX_FRAME_SKIPS = 5; // maximum number of frames to be skipped
private final static int FRAME_PERIOD = 1000 / MAX_FPS; // the frame period
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
private SurfaceHolder mSurfaceHolder;
private MainGameBoard mMainGameBoard;
public MainThread(SurfaceHolder surfaceHolder, MainGameBoard gameBoard) {
super();
mSurfaceHolder = surfaceHolder;
mMainGameBoard = gameBoard;
}
@Override
public void run() {
Canvas mCanvas;
Log.d(TAG, "Starting game loop");
long beginTime; // the time when the cycle begun
long timeDiff; // the time it took for the cycle to execute
int sleepTime; // ms to sleep (<0 if we're behind)
int framesSkipped; // number of frames being skipped
sleepTime = 0;
while(running) {
mCanvas = null;
try {
mCanvas = this.mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder) {
beginTime = System.currentTimeMillis();
framesSkipped = 0;
this.mMainGameBoard.update();
this.mMainGameBoard.render(mCanvas);
timeDiff = System.currentTimeMillis() - beginTime;
sleepTime = (int) (FRAME_PERIOD - timeDiff);
if(sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
}
while(sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
// catch up - update w/o render
this.mMainGameBoard.update();
sleepTime += FRAME_PERIOD;
framesSkipped++;
}
}
} finally {
if(mCanvas != null)
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
MainGameBoard.java:
public class MainGameBoard extends SurfaceView implements
SurfaceHolder.Callback {
private MainThread mThread;
private PlayerCharacter mPlayer;
private Obstacle[] mObstacleArray = new Obstacle[4];
public static final String TAG = MainGameBoard.class.getSimpleName();
private long width, height;
private boolean gameStartedFlag = false, gameOver = false, update = true;
private Paint textPaint = new Paint();
private int scoreCount = 0;
private Obstacle collidedObs;
public MainGameBoard(Context context) {
super(context);
getHolder().addCallback(this);
DisplayMetrics displaymetrics = new DisplayMetrics();
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
height = displaymetrics.heightPixels;
width = displaymetrics.widthPixels;
mPlayer = new PlayerCharacter(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher), width/2, height/2);
for (int i = 1; i <= 4; i++) {
mObstacleArray[i-1] = new Obstacle(width*(i+1) - 200, height, i);
}
mThread = new MainThread(getHolder(), this);
setFocusable(true);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mThread.setRunning(true);
mThread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Surface is being destroyed");
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
while (retry) {
try {
mThread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
Log.d(TAG, "Thread was shut down cleanly");
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
if(update && !gameOver) {
if(gameStartedFlag) {
mPlayer.cancelJump();
mPlayer.setJumping(true);
}
if(!gameStartedFlag)
gameStartedFlag = true;
}
}
return true;
}
@SuppressLint("WrongCall")
public void render(Canvas canvas) {
onDraw(canvas);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.GRAY);
mPlayer.draw(canvas);
for (Obstacle obs : mObstacleArray) {
obs.draw(canvas);
}
if(gameStartedFlag) {
textPaint.reset();
textPaint.setColor(Color.WHITE);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(100);
canvas.drawText(String.valueOf(scoreCount), width/2, 400, textPaint);
}
if(!gameStartedFlag && !gameOver) {
textPaint.reset();
textPaint.setColor(Color.WHITE);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(72);
canvas.drawText("Tap to start", width/2, 200, textPaint);
}
if(gameOver) {
textPaint.reset();
textPaint.setColor(Color.WHITE);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(86);
canvas.drawText("GAME OVER", width/2, 200, textPaint);
}
}
public void update() {
if(gameStartedFlag && !gameOver) {
for (Obstacle obs : mObstacleArray) {
if(update) {
if(obs.isColidingWith(mPlayer)) {
collidedObs = obs;
update = false;
gameOver = true;
return;
} else {
obs.update(width);
if(obs.isScore(mPlayer))
scoreCount++;
}
}
}
if(!mPlayer.update() || !update)
gameOver = true;
}
}
}
玩家角色.java:
public void draw(Canvas canvas) {
canvas.drawBitmap(mBitmap, (float) x - (mBitmap.getWidth() / 2), (float) y - (mBitmap.getHeight() / 2), null);
}
public boolean update() {
if(jumping) {
y -= jumpSpeed;
jumpSpeed -= startJumpSpd/20f;
jumpTick--;
} else if(!jumping) {
if(getBottomY() >= startY*2)
return false;
y += speed;
speed += startSpd/25f;
}
if(jumpTick == 0) {
jumping = false;
cancelJump(); //rename
}
return true;
}
public void cancelJump() { //also called when the user touches the screen in order to stop a jump and start a new jump
jumpTick = 20;
speed = Math.abs(jumpSpeed);
jumpSpeed = 20f;
}
Obstacle.java:
public void draw(Canvas canvas) {
Paint pnt = new Paint();
pnt.setColor(Color.CYAN);
canvas.drawRect(x, 0, x+200, ySpaceStart, pnt);
canvas.drawRect(x, ySpaceStart+500, x+200, y, pnt);
pnt.setColor(Color.RED);
canvas.drawCircle(x, y, 20f, pnt);
}
public void update(long width) {
x -= speed;
if(x+200 <= 0) {
x = ((startX+200)/(index+1))*4 - 200;
ySpaceStart = r.nextInt((int) (y-750-250+1)) + 250;
scoreGiven = false;
}
}
public boolean isColidingWith(PlayerCharacter mPlayer) {
if(mPlayer.getRightX() >= x && mPlayer.getLeftX() <= x+20)
if(mPlayer.getTopY() <= ySpaceStart || mPlayer.getBottomY() >= ySpaceStart+500)
return true;
return false;
}
public boolean isScore(PlayerCharacter mPlayer) {
if(mPlayer.getRightX() >= x+100 && !scoreGiven) {
scoreGiven = true;
return true;
}
return false;
}
synchronize
并锁定画布。这样做会引发各种问题。你的动画循环并不是很合理。 - Gene