Lags / gagas irritantes em um jogo android

Comecei com o desenvolvimento de jogos no Android e estou trabalhando em um jogo super simples.

O jogo é basicamente como pássaro flappy.

Consegui fazer com que tudo funcionasse, mas tenho muitas gagueiras e atrasos.

O telefone que estou usando para testes é o LG G2, portanto, ele deve rodar jogos muito mais pesados e complexos do que isso.

Basicamente, existem 4 'obstáculos' com uma largura de tela cheia separados um do outro.
Quando o jogo começa, os obstáculos começam a se mover (em direção ao personagem) a uma velocidade constante. O valor x do personagem do jogador é consistente durante todo o jogo, enquanto o valor y muda.

O atraso ocorre principalmente quando o personagem passa por um obstáculo (e às vezes um pouco depois desse obstáculo também). O que acontece é que há atrasos desiguais em cada desenho do estado do jogo, causando gagueira nos movimentos.

O GC não é executado de acordo com o log.As gagueiras NÃO são causadas pela velocidade ser muito alta (eu sei disso porque, no início do jogo, quando os obstáculos estão fora de vista, o personagem se move suavemente)Eu não acho que o problema também esteja relacionado ao FPS, porque mesmo quando o campo MAX_FPS está definido como 100, ainda há falhas.

Meu pensamento é que existe uma linha ou várias linhas de código que causam algum tipo de atraso (e, portanto, os quadros são ignorados). Eu também acho que essas linhas devem estar em torno doupdate() edraw() métodos dePlayerCharacter, ObstacleeMainGameBoard.

O problema é que ainda sou novo no desenvolvimento e no androidjogos desenvolvimento especificamente, então não tenho idéia do que poderia causar esse atraso.

Tentei procurar respostas on-line ... Infelizmente, tudo o que achei apontou a culpa do GC. No entanto, como eu não acredito que seja o caso (corrija-me se estiver errado), essas respostas não se aplicam a mim. Eu também li o desenvolvedor do AndroidPerformance Tips página, mas não conseguiu encontrar nada que ajudasse.

Então, por favor, me ajude a encontrar a resposta para resolver esses atrasos irritantes!

Algum código

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;
    }
}

}

PlayerCharacter.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;
}

questionAnswers(5)

yourAnswerToTheQuestion