Gleichmäßige Bewegung

  • Antworten:4
Alex F.
  • Forum-Beiträge: 6

27.07.2013, 23:47:07 via Website

Ich bin dabei ein Spiel mit Java zu programmieren. Ich habe aber ein Problem mit der Performance. Ein einfaches Objekt als Bitmap soll sich relativ schnell auf dem Bildschirm bewegen. Es funktioniert aber nie flüssig. Immer gibt es kleine Ruckler oder Sprünge in der Bewegung, es läuft jedenfalls nicht dauerhaft gleichmäßig wie man es sonst von Android Spielen kennt.

Ich rufe in einer Schleife „canvas.drawBitmap(bmp, x, y, null)“ auf und verändere anschließend beispielsweise die y-Koordinate um 10 Einheiten, damit es die passende Geschwindigkeit hat, aber die Bewegung ist nicht flüssig. Es funktioniert weder mit fester Framerate wie in meinem Code unten, noch mit variabler Framerate, wo die Koordinate abhängig von der Dauer des Schleifendurchlaufs verändert wird. Das Objekt soll sich unabhängig vom jeweiligen Handy immer gleich schnell bewegen.

Über eine kleine Hilfe für einen funktionierenden Code ohne Ruckler würde ich mich freuen


1public class MainActivity extends Activity {
2
3 @Override
4 protected void onCreate(Bundle savedInstanceState) {
5 super.onCreate(savedInstanceState);
6 setContentView(new Gameview(this));
7
8 }
9
10}

1public class Gameview extends SurfaceView{
2
3 private SurfaceHolder surfaceHolder;
4 private Bitmap bmp;
5 private GameLoopThread theGameLoopThread;
6
7 int x = 200;
8 int y = 1200;
9
10 public Gameview(Context context) {
11 super(context);
12 theGameLoopThread = new GameLoopThread(this);
13 surfaceHolder = getHolder();
14
15 surfaceHolder.addCallback(new SurfaceHolder.Callback() {
16
17 public void surfaceDestroyed(SurfaceHolder holder) {
18 boolean retry = true;
19 theGameLoopThread.setRunning(false);
20 while(retry){
21 try {
22 theGameLoopThread.join();
23 retry=false;
24 }catch(InterruptedException e){
25
26 }
27 }
28
29 }
30
31 public void surfaceCreated(SurfaceHolder holder) {
32 theGameLoopThread.setRunning(true);
33 theGameLoopThread.start();
34 }
35
36 public void surfaceChanged(SurfaceHolder holder, int format,
37 int width, int height) {
38
39 }
40 });
41
42
43 bmp = BitmapFactory.decodeResource(getResources(),
44 R.drawable.ic_launcher);
45 }
46 @Override
47 public void draw(Canvas canvas) {
48
49 canvas.drawColor(Color.DKGRAY);
50
51 canvas.drawBitmap(bmp, x, y, null);
52
53 y = y-10;
54
55 if(y<=0){y=1200;}
56
57 }
58
59}

1public class GameLoopThread extends Thread {
2
3 static final long FPS = 30; //feste Framerate
4 private GameView theView;
5 private boolean isRunning = false;
6 long TPS = 1000 / FPS;
7 long startTime, sleepTime;
8 Canvas theCanvas = null;
9
10 public GameLoopThread(GameView theView) {
11 this.theView = theView;
12 }
13
14 public void setRunning(boolean run) {
15 isRunning = run;
16 }
17
18 @Override
19 public void run() {
20
21
22 while (isRunning) {
23
24 startTime = System.currentTimeMillis();
25
26 try {
27 theCanvas = theView.getHolder().lockCanvas();
28 synchronized (theView.getHolder()) {
29 theView.draw(theCanvas); //Hier wird draw aufgerufen
30 }
31 } finally {
32 if (theCanvas != null) {
33 theView.getHolder().unlockCanvasAndPost(theCanvas);
34 }
35 }
36
37 sleepTime = TPS - (System.currentTimeMillis() - startTime);
38 try {
39 if (sleepTime > 0) //Für unabhängige Geschwindigkeit
40 try {
41 Thread.sleep(sleepTime);
42 } catch (InterruptedException e) {}
43 else
44 sleep(1);
45
46 } catch (Exception e) {
47
48 }
49
50 }
51 }
52}

Antworten
impjor
  • Forum-Beiträge: 1.793

28.07.2013, 00:52:32 via App

Ich würde dir raten, konstant sleep(15); (z.B.) zu machen und dann nicht y -= 10;
sondern
long lastTime = -1;
[...]
if (lastTime == -1)
lastTime = System.currentTimeMillis();
long delta = System.currentTimeMillis - lastTime;
y -= 10 * delta / 1000;

Dann sollte es sich gleichmäßig 10px pro Sekunde bewegen.

Gruß

Liebe Grüße impjor.

Für ein gutes Miteinander: Unsere Regeln
Apps für jeden Einsatzzweck
Stellt eure App vor!

Antworten
Alex F.
  • Forum-Beiträge: 6

28.07.2013, 10:59:34 via Website

Der Code funktioniert leider nicht. Das Objekt bewegt sich gar nicht. Diese Methode habe ich aber auch etwas anders ausprobiert, es entspricht nämlich der variablen Framerate. Je nachdem wie lange ein Schleifendurchlauf dauert, bewegt sich das Objekt entsprechend einem Faktor delta. Das Problem ist hierbei, dass ein Schleifendurchlauf nicht immer die gleiche Zeit benötigt, dadurch bewegt sich das Objekt mal schneller, mal langsamer, da delta mal höher und mal niedriger ausfällt.

Antworten
Mac Systems
  • Forum-Beiträge: 1.727

28.07.2013, 13:43:52 via Website

Das wird auch nur auf deinem Device laufen, sobald du andere Phones testet fliegt das. Du musst mit delta arbeiten da das System nicht 100% genau die threads wieder einen time slot zuweißt. Ich rate dir ein wenig basic zu Animationen zu lesen.

Da ist ein weiteres Probem im Code:

1public void setRunning(boolean run) {
2isRunning = run;
3}

Das muss entweder per volatile oder per synchronized gesetzt werden:

1synchronized public void setRunning(boolean run) {
2isRunning = run;
3}

oder


1private volatile boolean isRunning = false;
2
3public void setRunning(boolean run) {
4isRunning = run;
5}

Nur so ist sicher das alle Threads auch die werte richtig sehen und es nicht zu komischen verhalten kommt.
volatile kann man hier benutzt da es boolean einen atomaren zugriff erlaubt, das gewährleistet die JVM. Andere Datentypen erlauben das in der form nicht.

Gruß von den Kanaren,
Mac

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

Antworten
Gelöschter Account
  • Forum-Beiträge: 442

29.07.2013, 21:28:30 via App

Ich empfehle dir statt Threads Runnables mit Handlern zu benutzen: (Nach meinen Erfahrungen extrem performancesparend)
1public boolean running = true;
2public int speed = 45;
3Handler handler = new Handler();
4Runnable runner;
5handler.postDelayed(runner = new Runnable() {
6@Override
7public void run() {
8//Ur Stuff
9if(running) {
10handler.postDelayed(this, speed);
11}
12}}, speed);
13if(running) {
14handler.postDelayed(runner, speed);
15}
16
17public boolean getRunning() {
18return running;
19}
20
21public void setRunning(boolean running) {
22this.running = running;
23}

— geändert am 29.07.2013, 21:32:22

Hi, bin 13 Jahre alt :D Erstes Android Spiel "Strategic Labyrinth": http://goo.gl/Q0Wbd

Antworten