Ärgerliche Verzögerungen / Stottern in einem Android-Spiel

Ich habe gerade mit der Spieleentwicklung in Android begonnen und arbeite an einem supereinfachen Spiel.

Das Spiel ist im Grunde wie ein Flappy Bird.

Ich habe es geschafft, alles zum Laufen zu bringen, aber ich bekomme viele Stottern und Verzögerungen.

Das Telefon, das ich zum Testen benutze, ist das LG G2, daher sollte und kann es Spiele ausführen, die viel schwerer und komplexer sind als dieses.

Grundsätzlich gibt es 4 "Hindernisse", die eine volle Bildschirmbreite voneinander entfernt sind.
Wenn das Spiel beginnt, bewegen sich die Hindernisse mit konstanter Geschwindigkeit (auf den Charakter zu). Der x-Wert des Spielercharakters ist während des gesamten Spiels konsistent, während sich sein y-Wert ändert.

Die Verzögerung tritt hauptsächlich dann auf, wenn der Charakter ein Hindernis passiert (und manchmal auch kurz danach). Was passiert ist, dass es ungleichmäßige Verzögerungen in jeder Zeichnung des Spielzustands gibt, was zu Rucklern in den Bewegungen führt.

Der GC wird nicht gemäß dem Protokoll ausgeführt.Das Ruckeln wird NICHT durch zu hohe Geschwindigkeit verursacht (ich weiß, dass sich der Charakter zu Beginn des Spiels, wenn die Hindernisse außer Sichtweite sind, reibungslos bewegt)Ich glaube nicht, dass das Problem auch mit FPS zusammenhängt, denn selbst wenn das Feld MAX_FPS auf 100 gesetzt ist, gibt es immer noch Ruckler.

Mein Gedanke ist, dass es eine oder mehrere Codezeilen gibt, die eine gewisse Verzögerung verursachen (und daher Frames überspringen). Ich denke auch, dass diese Zeilen um die sein solltenupdate() unddraw() Methoden vonPlayerCharacter, Obstacle, undMainGameBoard.

Das Problem ist, ich bin immer noch neu in der Android-Entwicklung und AndroidSpiel Entwicklung speziell, so habe ich keine Ahnung, was eine solche Verzögerung verursachen könnte.

Ich habe versucht, online nach Antworten zu suchen ... Leider deutete alles, was ich fand, auf die Schuld des GC hin. Allerdings glaube ich nicht, dass dies der Fall ist (korrigiere mich, wenn ich falsch liege). Diese Antworten gelten nicht für mich. Ich habe auch die Android-Entwickler gelesenPerformance Tips Seite, konnte aber nichts finden, was geholfen hat.

Bitte helfen Sie mir, die Antwort auf diese lästigen Verzögerungen zu finden!

Ein bisschen Code

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

Antworten auf die Frage(5)

Ihre Antwort auf die Frage