GameLoop - Warum "while()"-Schleifen statt Timer?

  • Antworten:9
Don Pipo
  • Forum-Beiträge: 12

09.02.2011, 14:51:02 via Website

Hi!

Ich versuche gerade von Flash/AS3 auf Android/Java umzusteigen und bei allen Tutorials über Spieleentwicklung, die ich gefunden haben, wurde die GameLoop in einem Thread per "while()"-Schleife erstellt, welche dann die Spiellogik und das Rendering aktualisiert. Ungefähr so:

1public void run() {
2 Canvas c;
3 while (_run) {
4 c = null;
5 try {
6 c = _surfaceHolder.lockCanvas(null);
7 synchronized (_surfaceHolder) {
8 // simulate and render game here
9 }
10 } finally {
11 // don't leave the Surface in an
12 // inconsistent state
13 }
14 }
15}

In Flash habe ich noch nie eine GameLoop gesehen, die mit "while()" realisiert wurde. Ich selbst arbeite meist mit Timer-Klassen. Gibt es einen Android- oder Java-spezifischen Grund warum dies in den meisten Tutorials so gelöst wurde? Oder kann ich das auch getrost weiterhin mit Timer-Klassen erledigen, die in einem festen Intervall mein Spiel aktualisieren?

MfG,
Pipo

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

09.02.2011, 15:01:45 via Website

Genau weiss ich es nicht, aber spontan fällt mir da folgender Fall ein, wo ein fester Timer Probleme machen könnte.

Du hast ein Interval von 10ms ... oder lass es 100 sein.
Der Durchlauf bis alle Objekte gemalt sind, dauert aber 1500ms, weil dein Telefon grad eine SMS bekommt, oder sonstwas und deinen Paint-Thread suspended.

Der Timer würde die Paint Methode dann 150 mal aufrufen.
Die while Schleife würde dagegen erst wieder von vorne beginnen, wenn der vorherige Durchlauf beendet ist.

Speziell auf Geräten mit sehr wenig Leistung kann es passieren, dass der Timer häufiger zuschlägt, als das Gerät malen kann.

IMHO skaliert ein while also wesentlich besser mit der Leistung des Geräts.

— geändert am 09.02.2011, 15:02:38

Antworten
Mac Systems
  • Forum-Beiträge: 1.727

09.02.2011, 15:20:42 via Website

Timer machen wenig sinn bei Spielen, laut docu ist der SurfaceView ebenfalls von Seiten des Android OS kontrolliert, so kommt es nicht dazu der Thread die "CPU frisst", was er sonst schnell mal tun würde.
Hast du einen Timer kannst du statt auf den Timer zu hoffen (deren Auslösung ist leider meist nicht so genau) eine while schleife nutzen, mittels der das maximum an performance des System/Core zu bekommen ist. Dafür musst du deine Animationen allerdings Zeitabhängig Coden Also: Fliege in einer Sekunde von A nach B (z.b Linear Interpolieren auf Basis der Zeit die vergangen ist). Das führt dazu das wenn du eine bessere Cpu hast die Animation besser laufen wird. Kommt die CPU wegen besagtem SMS Empfang durcheinander ist das visuelle Ergebnis besser.


hth,
Mac

— geändert am 09.02.2011, 15:22:55

Windmate HD, See you @ IO 14 , Worked on Wundercar, Glass V3, LG G Watch, Moto 360, Android TV

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

09.02.2011, 15:35:49 via Website

Mir ist grad noch ein Licht aufgegangen :)

Ein weiterer Voteil:

Der Timer nutzt intern einen periodischen Thread, um Aktionen vom Typ Runnable aufzurufen.
Die laufen, dann aber im Thread des Timer und NICHT im Thread, in dem deine GUI läuft.
In Android kann man aber nur aus dem Thread, in dem die GUI läuft die visuellen Elemente verändern.
Das kann man durch die Methode runOnUiThread(Runnable) erreichen.

Man spart sich durch das while direkt in der Activity also auch noch die ewigen runOnUiThread() Aufrufe und damit viel Overhead.

— geändert am 09.02.2011, 15:37:10

Antworten
Mac Systems
  • Forum-Beiträge: 1.727

09.02.2011, 15:52:31 via Website

Das stimmt so nicht, da gerade der SurfaceView mit jeder art von Thread "bemalt" werden darf. Da hier in der run Methode die while liegt und an den Thread keine weiteren Anforderungen gestellt werden sehe ich das so....

Windmate HD, See you @ IO 14 , Worked on Wundercar, Glass V3, LG G Watch, Moto 360, Android TV

Antworten
Don Pipo
  • Forum-Beiträge: 12

09.02.2011, 16:52:47 via Website

Vielen Dank schon einmal für eure Antworten. Die Antwort mit der besseren Skalierbarkeit von "while()" leuchtet ein.

Leider hatte ich noch vergessen zu erwähnen, dass ich den Timer nur dafür genutzt hätte,um die Spiellogik zu aktualisieren. Das Rendering würde ich über die "onDrawFrame()"-Funktion von GSSurfaceView.Renderer aktualisieren - falls das einen Unterschied bei euren bisherigen Antworten macht. (Gibt es so etwas wie "onDrawFrame()" - also ein kontinuierliches rendern - eigentlich auch bei anderen Views?)

Da ich nur ~25 Aktualisierungen meiner Spiellogik pro Sekunde brauche und die while()-Schleife wohl möglich mehr Aktualisierungen pro Sekunden auslöst, würde ich da noch einen kleinen Stopmechanismus einbauen. Meine GameLoop würde also so aussehen (Pseudocode):

1GameActivity
2{
3 onCreate
4 {
5 // myView = new GLSurfaceView()
6 // myView.setRenderer(new GameRenderer())
7 // setContentView(myView )
8 // myGameLogicThread = new GameLogicThread()
9 }
10}
11
12GameRenderer
13{
14 onDrawFrame
15 {
16 // update rendering logic
17 }
18}
19
20GameLogicThread
21{
22 run
23 {
24 while(running && timeToUpdate())
25 {
26 // update game logic
27 }
28 }
29}


Wobei "timeToUpdate()" in GameLogicThread kontrolliert, wie viel Zeit seit dem eltzten Update vergangene ist und entsprechend false oder true zurückgibt, damit sich das Spiel bspw. nur 25 Mal pro Sekunde aktualisiert.

Was meint ihr? Ist das sinnvoll?

— geändert am 09.02.2011, 16:53:12

Antworten
Don Pipo
  • Forum-Beiträge: 12

09.02.2011, 19:28:39 via Website

Ja, na klar^^° Zu schnell getippt, zu langsam nachgedacht.
Aber davon abgesehen? Sieht ja eigentlich ganz robust aus.

Antworten