Ärgerlich laggt/ruckelt in einem android Spiel
Ich begann mit der Spieleentwicklung in android, und ich bin auf ein super einfaches Spiel.
Das Spiel ist im Prinzip wie flappy bird.
Ich es geschafft, alles funktioniert, aber ich bekomme eine Menge stottert und hinkt.
Dem Handy bin ich mit für das testen LG G2, so sollte es und laufen tut Spiele viel schwerer und komplexer als dieser.
Grundsätzlich gibt es 4 "Hindernisse", die eine volle Bildschirm-Breite voneinander entfernt.
Wenn das Spiel beginnt, die Hindernisse beginnen sich zu bewegen (in Richtung der Zeichen) mit einer Konstanten Geschwindigkeit. Der Spieler-Charakter ist der x-Wert setzt sich durch das ganze Spiel, während der y-Wert ändert.
Die Verzögerung tritt auf, vor allem, wenn der Charakter geht durch ein Hindernis (und manchmal ein wenig nach, das Hindernis zu). Was passiert, ist, dass es ungleiche Verzögerungen in jeder Zeichnung der Spiel-Status verursacht stottert in den Bewegungen.
- GC läuft nicht laut log.
- Der stottert NICHT, verursacht durch die Geschwindigkeit nicht zu hoch werden (ich weiß, dass da am Anfang des Spiels, wenn die Hindernisse sind aus der Sicht der Charakter bewegt sich reibungslos)
- Ich glaube nicht, dass das problem ist FPS Verwandte zu haben, denn auch wenn die MAX_FPS Feld auf 100 eingestellt ist, gibt es immer noch stottert.
Mein Gedanke ist, dass es eine Zeile oder mehrere Zeilen code, die dazu führen, dass einige ein bisschen Verzögerung passieren (und somit frames übersprungen). Ich denke auch, dass diese Zeilen werden sollten, um die update()
und draw()
Methoden der PlayerCharacter
, Obstacle
, und MainGameBoard
.
Das problem ist, ich bin noch neu in android Entwicklung und android Spiel Entwicklung speziell, so dass ich keine Ahnung habe, was könnte die Ursache dieser Verzögerung.
Ich habe versucht, online-Suche für Antworten... Leider alles, was ich fand, zeigte über die GC Verschulden. Jedoch, wie ich glaube nicht, dass es der Fall ist (korrigieren Sie mich wenn ich bin falsch) diejenigen, die Antworten gelten nicht für mich. Ich habe auch gelesen das android-developer ' s Performance Tips
Seite, konnte aber nichts finden, das hat geholfen.
So, bitte helfen Sie mir, finden Sie die Antwort auf die Lösung dieser nervigen lags!
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;
}
- Profil, das beispielsweise mit DDMS-Methode-profiling: software.intel.com/en-us/articles/... oder anderen Techniken. Weder das Protokoll noch einige magic MAX FPS (und max bedeutet nicht, dass es immer so laufen, dass schnell, es ist nur, wo es caps) wird Ihnen sagen, ein richtigen Grund.
- Fehlende Konstruktoren in Ihrem Obstacle.java und PlayerCharacter.java Klassen
- Sicherlich sollten Sie nicht verwenden
synchronize
Führung und Aufbewahrung der Leinwand gesperrt, während der Haupt-thread schlafen. Diese verlangt für alle Arten von Schwierigkeiten. Die animation Schleife macht nicht viel Sinn.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Update: so genau Wie das war, es kaum an der Oberfläche gekratzt. Eine genauere Erklärung ist jetzt verfügbar. Die game loop Beratung ist in Anhang A. Wenn Sie wirklich wollen, zu verstehen, was Los ist, beginnen mit, dass.
Original-Beitrag folgt...
Ich werde beginnen mit einer Kapsel Zusammenfassung, wie die Grafik-pipeline in Android funktioniert. Finden Sie weitere Gründliche Behandlungen (z.B. einige schön detaillierte Google-I/O-Gespräche), ich bin also nur schlagen die hohen Punkte. Es stellte sich heraus, eher länger, als ich erwartet hatte, aber ich wollte schon schreiben, der dies für eine Weile.
SurfaceFlinger
Ihre Anwendung zieht nicht, auf Den Framebuffer. Einige Geräte gar nicht haben Den Framebuffer. Ihre Anwendung hält den "Hersteller" - Seite ein
BufferQueue
Objekt. Wenn es abgeschlossen hat, die das Rendern eines Frames, ruft esunlockCanvasAndPost()
odereglSwapBuffers()
, die Warteschlangen den fertigen buffer für die Anzeige. (Technisch-rendering kann auch nicht beginnen, bis Sie sagen, es zu tauschen und kann fortgesetzt werden, während der Puffer sich durch die pipeline, aber das ist eine andere Geschichte.)Der Puffer gesendet wird, um die "consumer" - Seite der Warteschlange, die in diesem Fall ist SurfaceFlinger, die system-Oberfläche compositor. Puffer sind vergangen, Griff; die Inhalte werden nicht kopiert. Jedes mal, wenn die display-refresh (nennen wir es mal "VSYNC") beginnt, SurfaceFlinger sieht bei all den verschiedenen Warteschlangen, um zu sehen, was Puffer verfügbar sind. Wenn es feststellt, dass neue Inhalte die Riegel der nächste Puffer aus, dass die Warteschlange. Falls nicht, verwendet es, was es bekam zuvor.
Die Sammlung von windows - (oder "Schichten"), die sichtbare Inhalte werden dann miteinander kombiniert. Dies kann durch die SurfaceFlinger (mit OpenGL ES gerendert Schichten in einen neuen buffer) oder durch den Hardware-Composer-HAL. Die hardware-Komponist (verfügbar auf den meisten neueren Geräte) wird durch die hardware-OEM, und bieten eine Reihe von "overlay" - Flugzeuge. Wenn SurfaceFlinger hat drei Fenster angezeigt, und der HDS hat drei overlay-Ebenen zur Verfügung, Sie legt die einzelnen Fenster werden in einem overlay, und nicht die Zusammensetzung wie das Bild angezeigt wird. Es gibt nie einen Puffer, der alle Daten enthält. Dies ist im Allgemeinen effizienter, als die gleiche Sache in der GLES. (Übrigens, dies ist, warum Sie nicht schnappen Sie sich einen Screenshot auf den meisten neueren Geräte, indem Sie einfach den framebuffer-dev-Eintrag und Lesen Pixel).
Also das ist, was die consumer-Seite aussieht. Kann man es selbst mit
adb shell dumpsys SurfaceFlinger
. Gehen wir zurück zu den Produzenten (d.h. Ihre app).der Produzent
Du bist mit einem
SurfaceView
, die aus zwei teilen: eine transparente Sicht auf das Leben mit der system UI, und eine separate Schicht, die alle Ihre eigenen. DieSurfaceView
's Oberfläche geht direkt an SurfaceFlinger, das ist, warum es hat viel weniger Aufwand als bei anderen Methoden (wieTextureView
).Die BufferQueue für die
SurfaceView
's Oberfläche ist dreifach gepuffert. Das heißt, Sie kann mit einem Puffer gescannt wird, wird für das display, ein Puffer, der sitzt an SurfaceFlinger wartet auf den nächsten VSYNC, und ein Puffer für Ihre app zu zeichnen. Mit mehr Puffer verbessert Durchsatz und glättet Unebenheiten, erhöht aber die Latenz zwischen, wenn Sie den Bildschirm berühren, und wenn Sie sehen, ein update. Zusätzliche Pufferung der ganze Rahmen auf der Oberseite dieses wird meist nicht tun Sie viel gutes.Zieht man schneller, als das display darstellen kann, frames, Sie füllt sich die Warteschlange, und Ihr Puffer-swap-call (
unlockCanvasAndPost()
) angehalten wird. Dies ist eine einfache Möglichkeit, um Ihren Spiel-update-rate die gleiche wie die display-rate-ziehen so schnell wie Sie können, und lassen Sie das system langsam nach unten. Jeder frame, den Sie vorher Stand, je nachdem wie viel Zeit verstrichen ist. (Ich habe diesen Ansatz in Android Breakout.) Es ist nicht ganz richtig, aber bei 60fps, die Sie nicht wirklich bemerken die Mängel. Erhalten Sie den gleichen Effekt mitsleep()
Anrufe, wenn Sie nicht schlafen lange genug -- Sie werden nur aufwachen, warten auf die Warteschlange. In diesem Fall gibt es keinen Vorteil zu schlafen, denn schlafen auf der Warteschlange ist gleich effizient.Wenn Sie ziehen langsamer als die Anzeige kann Rendern von frames, die Warteschlange wird schließlich trocken laufen, und SurfaceFlinger zeigt das gleiche Bild auf zwei aufeinander folgende Anzeige aktualisiert. Dies geschieht regelmäßig, wenn Sie versuchen, das Tempo, mit dem Sie Ihr Spiel mit
sleep()
Anrufe und du schläfst zu lange. Es ist unmöglich exakt auf die display-refresh-rate, aus theoretischen Gründen (es ist schwer zu realisieren, eine PLL ohne einen feedback-Mechanismus) und praktischen Gründen (die refresh-rate kann im Laufe der Zeit ändern, z.B. ich habe gesehen, es variiert von 58fps auf 62fps auf einem Gerät).Mit
sleep()
fordert in einer game loop das Tempo, mit dem Sie Ihre animation ist eine schlechte Idee.gehen, ohne Schlaf
Haben Sie ein paar Möglichkeiten. Sie können verwenden Sie die "draw so schnell wie Sie können, bis der Puffer-swap call-backs up" - Ansatz, das ist es, was eine Menge von apps auf Basis
GLSurfaceView#onDraw()
tun (ob Sie es wissen oder nicht). Oder Sie können Choreograph.Choreografin können Sie einen callback, der ausgelöst wird, auf den nächsten VSYNC. Wichtig ist, das argument an den callback ist die eigentliche VSYNC Zeit. Also, auch wenn Ihre app nicht aufwachen sofort, Sie haben noch ein genaues Bild, wenn die display-refresh-begann. Dies erweist sich als sehr nützlich bei der Aktualisierung Ihrer Spiel-Status.
Den code, der Aktualisierungen Spiel Staat sollte sich nie entwickelt, um Voraus "frame". Angesichts der Vielfalt der Geräte und die unterschiedlichen Bildwiederholraten, die einem einzelnen Gerät verwenden können, können Sie nicht wissen, was ein "frame" ist. Ihr Spiel spielen, etwas langsam oder etwas schneller-oder wenn Sie Glück haben und jemand versucht, es zu spielen auf einem TV-Gerät gesperrt werden, um 48Hz über HDMI, Sie werden ernst träge. Sie müssen die Zeit bestimmen, die Differenz zwischen dem vorangegangenen frame und dem aktuellen frame, und die Förderung der Spiel-Status angemessen.
Dies erfordert möglicherweise ein bisschen eine mentale Umorientierung, aber es lohnt sich.
Können Sie dies in Aktion zu sehen in Breakout, die Fortschritte der ball position basierend auf der verstrichenen Zeit. Es schneidet große Sprünge in der Zeit in kleinere Stücke zu halten, die Kollisionsabfrage einfach. Das Problem mit Breakout ist, dass es mit dem Zeug-die-Warteschlange-voll-Ansatz, die Zeiten sind abhängig von Variationen in der Zeit, die erforderlich ist für SurfaceFlinger zu arbeiten. Auch, wenn der Puffer-Warteschlange anfangs leer ist, kannst du Sie Absenden frames sehr schnell. (Das heißt, Sie berechnen zwei frames mit fast null-Zeit-delta, aber Sie sind immer noch an der Anzeige bei 60fps. In der Praxis werden Sie nicht sehen diese, weil der Zeitstempel Unterschied ist so klein, dass es gerade so aussieht, wie das gleiche Bild gezeichnet, zweimal, und es geschieht nur, wenn der übergang von der nicht-Animation zu animieren, so dass Sie nicht sehen, nichts Ruckeln.)
Choreografin, Sie erhalten den tatsächlichen VSYNC Zeit, so erhalten Sie einen schönen regelmäßigen Takt auf Basis Ihrer zeitlichen Abständen aus. Da bist du mit dem display-refresh-Zeit clock-Quelle, die Sie nie bekommen aus mit dem display synchronisiert werden.
Natürlich, Sie noch müssen, um vorbereitet zu sein auf die Bilder fallen.
keine frame Links hinter
Einer Weile habe ich ein screen-recording-demo zu Grafika ("Rekord GL app"), die nicht sehr einfache animation-nur ein flat-shaded Prellen Rechteck und ein sich drehendes Dreieck. Es verbessert die Staats-und zieht, wenn der Choreograf Signale. Ich codiert es, lief es... und begann zu bemerken, Choreografin Rückrufe sichern.
Nach dem Graben mit systrace, entdeckte ich, dass die framework-UI war gelegentlich dabei einige layout-Arbeit (wahrscheinlich zu tun mit den Tasten und den text in der UI-Schicht, die sitzt oben auf der
SurfaceView
Oberfläche). Normalerweise nahm 6ms, aber wenn ich mich nicht aktiv bewegt meine finger auf dem Bildschirm, mein Nexus 5 verlangsamt die verschiedenen Uhren, den Stromverbrauch zu reduzieren und die Batterielaufzeit weiter zu verbessern. Die re-layout nahm 28ms statt. Beachten Sie, dass ein 60fps frame ist 16.7 ms.Den GL-rendering war fast augenblicklich, aber der Choreograph update geliefert wird der UI-thread, das war das Schleifen Weg auf das layout, so dass mein renderer thread hat nicht das signal bekommen, bis viel später. (Man kann Choreografin liefern das signal direkt an den renderer thread, aber da ist ein Fehler in der Choreograf, der einen Speicherverlust verursachen, wenn Sie tun.) Das Update war, um die drop-frames, wenn die aktuelle Zeit ist mehr als 15ms nach dem VSYNC-Zeit. Die app, der immer noch die state update -- die Kollisionserkennung ist so rudimentär, dass komische Sachen passiert, wenn Sie lassen Sie die Zeit, die Lücke zu groß-aber es hat nicht Einreichen, einen Puffer zu SurfaceFlinger.
Während der Ausführung der app können Sie sagen, Wann frames verworfen werden, weil Grafika blinkt die Umrandung rot und updates ein Zähler auf dem Bildschirm. Sie können nicht sagen, durch die Beobachtung der animation. Da der Status-updates sind auf Zeitabständen, nicht frame zählt, alles bewegt sich nur so schnell, wie es wäre, ob das frame gelöscht wurde oder nicht, und bei 60fps haben Sie nicht bemerkt, ein einziges dropped frame. (Das hängt zum Teil auf Ihre Augen, das Spiel, und die Eigenschaften der Anzeige-hardware).
Key lessons:
Zeichnung
Rendering auf eine Leinwand, kann sehr effektiv sein, wenn es hardware-beschleunigt. Wenn nicht, und du machst die Zeichnung in der software, kann es eine Weile dauern-vor allem, wenn Sie Sie berühren, sind viele Pixel.
Zwei wichtige bits zu Lesen: erfahren Sie mehr über hardware-beschleunigtes Rendern, und mit dem hardware-scaler zur Verringerung der Zahl der Pixel muss die app touch. Die "Hardware-scaler exerciser" in Grafika wird Ihnen ein Gefühl für das, was passiert, wenn Sie verringern Sie die Größe Ihrer Zeichnung Oberfläche-Sie können ziemlich klein, bevor sich die Effekte bemerkbar machen. (Ich finde es seltsam amüsant zu beobachten, GL render ein sich drehendes Dreieck in einem 100x64 Oberfläche).
Können Sie auch einige der Rätsel aus dem rendering durch die Verwendung von OpenGL ES direkt. Es ist ein bisschen eine Beule zu lernen, wie die Dinge funktionieren, aber die Breakout - (und für eine aufwändigere Beispiel, Replica Island) zeigen Sie alles, was Sie für ein einfaches Spiel.
{~ignore} Chor test
im Grafika (github.com/google/grafika/blob/master/src/com/android/grafika/...) wurde geschrieben, um die Ausübung des Verhaltens. IIRC, es verhindert, dass die Aktivität freigesetzt werden, wenn Sie schalten Weg. Für das NDK, wie Sie sagten, Sie schreiben die callback-in Java-code und signal der nativen thread.Ohne jemals gemacht zu haben, ein Spiel in Android, ich habe aus 2D-Spiele in Java/AWT mit Canvas und bufferStrategy...
Wenn Sie flackern, Sie könnte immer gehen für einen manuellen double-buffer (get-rid-of flackern) durch Rendern in eine offscreen-Bild und dann nur page-flip /drawImage mit der neuen pre-gerenderten Inhalt direkt.
Aber, ich bekomme das Gefühl, dass Sie mehr besorgt über die "smoothness" in der animation, in dem Fall würde ich empfehlen, dass Sie verlängern Sie Ihren code mit interpolation zwischen den verschiedenen Animations-Zecken;
Derzeit die rendering-loop-update logischen Zustand (Dinge bewegen, logischerweise) im selben Tempo, wie Sie machen, und die Maßnahme mit einigen Referenz-Zeit und versuchen Sie zu verfolgen, von der Zeit gegangen.
Stattdessen sollten Sie aktualisieren, in welcher Frequenz Sie sich fühlen ist es wünschenswert, für die "Logik" in Ihrem code zu arbeiten-in der Regel 10 oder 25Hz ist auch gut so (ich nenne es "update-ticks", die völlig Verschieden ist von den tatsächlichen FPS), während das rendering wird erreicht, indem hochauflösende Spur der Zeit für die Messung von "wie lange" Ihre eigentliche Rendern dauert (ich habe nanoTime, und das war völlig ausreichend, in der Erwägung, dass currentTimeInMillis ist, eher nutzlos...),
In dieser Weise können Sie interpolieren zwischen Zecken und rendert so viele Bilder wie möglich, bis der nächste tick von der Berechnung der feinkörnigen Position, basierend auf wie viel Zeit ist vergangen seit dem letzten tick, im Vergleich zu, wie viel Zeit es "sollte" zwischen zwei ticks (da Sie immer wissen, wo Sie sind - position, und da, wo du hingehst -- Geschwindigkeit)
Diese Weise erhalten Sie die gleiche "animation speed" - unabhängig von der CPU/Plattform, aber mehr oder weniger Glätte, da schnellere CPUs führen mehr macht zwischen den verschiedenen Zecken.
BEARBEITEN
Einige copy-paste/konzeptionellen code -- beachten Sie aber, dass dies AWT-und J2SE-kein Android. Jedoch, als Konzept und mit einigen Androidification ich bin mir sicher, dass dieser Ansatz leisten sollte reibungslos, es sei denn die Rechnung gemacht, in Ihrer Logik/update ist zu schwer (z.B. N^2-algorithmen für die Kollisionserkennung und N groß mit Partikel-Systeme und ähnliches).
Active render-loop
Anstatt repaint zu tun, die Malerei für Sie (das können auch andere Zeit, je nachdem, was OS macht), der erste Schritt ist die aktive Kontrolle über die rendering-Schleife und verwenden Sie die BufferStrategy, wo Sie gerendert und dann aktiv "zeigen" den Inhalt, wenn Sie fertig sind, bevor Sie zurück an ihm wieder.
Puffer-Strategie
Erfordern möglicherweise som speziellen Android-Zeug in Gang zu bringen, aber es ist ziemlich straight forward. Ich benutze 2-Seiten für die bufferStrategy zum erstellen einer "Seite-Umblättern" - Mechanismus.
Animation loop
Dann im main-loop, die Strategie und übernehmen die aktive Steuerung (keine streichen)!
Ein Objekt verwaltet werden, die über Haupt-Schleife sähe dann etwas entlang der Linien von;
Alle 2D-Positionen verwaltet werden, mit einem GfxPoint utility-Klasse mit doppelter Genauigkeit (da die interpolierten Bewegungen kann sehr fein sein und die Rundung ist in der Regel nicht wollten, bis das Rendern der eigentlichen Grafik). Zur Vereinfachung der mathematischen Sachen benötigt und macht den code besser lesbar, außerdem habe ich verschiedene Methoden.
BufferStrategy
wird es? Ich bin nicht sicher, dass es ein gleichwertiges in android... soweit ich/google weiß.Versuchen Sie diese auf für Größe. Sie bemerken Sie nur die sync-und lock-die Leinwand für die kürzeste Zeit. Ansonsten ist der OS wird entweder A) Fallen lassen, den Puffer, weil Sie zu langsam waren, oder B) nicht aktualisieren, bis Sie Ihren Schlaf zu warten, ist fertig.
Zunächst Canvas-Bereich ausführen können schlecht also nicht zu viel erwarten. Vielleicht möchten Sie versuchen, die lunarlander Beispiel aus dem SDK und sehen, was Leistung, die Sie erhalten auf Ihre hardware.
Verringern Sie die max fps nach unten, um so etwas wie 30, das Ziel ist zu glatt nicht schnell.
Auch loswerden der sleep aufruft, rendert die Leinwand wird wahrscheinlich genug schlafen. Versuchen Sie so etwas wie:
Wenn Sie möchten, können Sie Ihre
this.mMainGameBoard.update()
oft mehr, als Ihr machen.Edit: also da du sagst die Dinge langsam, wenn die Hindernisse erscheinen. Versuchen Sie, zeichnen Sie eine offscreen-Canvas /Bitmap. Ich habe gehört, dass einige der drawSHAPE Methoden CPU optimiert und Sie erhalten eine bessere Leistung, Zeichnung zu einem offline-canvas/bitmap da diese nicht hardware - /gpu-beschleunigt.
Edit2: Was bedeutet Leinwand.isHardwareAccelerated() zurück für Sie?
Einer der häufigsten Ursachen der Verlangsamung und Stottern in einem Spiel ist die Grafik-pipeline. Spiel Logik ist viel schneller zu verarbeiten, als es zu zeichnen (im Allgemeinen), so dass Sie wollen, stellen Sie sicher, dass Sie ziehen alles in die effizienteste Art und Weise möglich. Weiter unten finden Sie einige Tipps, wie Sie diese erreichen.
einige suggetion, es besser zu machen
https://www.yoyogames.com/tech_blog/30