Canvas Breite ermitteln im Konstruktor

Hallo,

kann mir jemand erklären, wie ich bereits im Konstruktor die Breite der canvas ermitteln kann? Ich würde das gerne dem Raumschiff mitgeben, damit das nicht immer halb über den Rand fliegt.
Ich hab es nicht hin bekommen im Konstruktor so was wie
canvas = ourHolder.lockCanvas();
breite = canvas.getWidth();
zu machen. Immer wenn ich die breite ermitteln will, stürzt die App ab. In der Run() Metode kann ich aber die Breite ermitteln.

Was mach ich denn falsch?

package de.fuckthesystem.thenewboston;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class SurfaceTestOneView extends SurfaceView implements Runnable {
   SurfaceHolder ourHolder;
   Thread ourThread = null;
   boolean isRunning = false;
   SpaceShip spaceship;
   GreenBall greenBall;
   SpaceBackground spaceBackground;
   
   int breite;
   boolean check = true;
   
   Canvas canvas; 
   
   public SurfaceTestOneView(Context context) {
      super(context);
      
      /*this.spaceship = spaceship;
      this.spaceBackground = spaceBackground;
      this.greenBall = greenBall;*/
      
      ourHolder = getHolder();
      
      spaceship = new SpaceShip(BitmapFactory.decodeResource(getResources(), R.drawable.spaceship), 100, 100);
      spaceBackground = new SpaceBackground(BitmapFactory.decodeResource(getResources(), R.drawable.verticalspace),50);
      greenBall = new GreenBall(BitmapFactory.decodeResource(getResources(), R.drawable.greenball),50,50); 
   }
   
   public void pause() {
      isRunning = false;
      while(true) {
         try {
            ourThread.join();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         break;
      }
      ourThread = null;
   }
   
   public void resume() {
      isRunning = true;
      ourThread = new Thread(this);
      ourThread.start();
   }
   
   @Override
   public void run() {
      
      long lastFrame = System.currentTimeMillis();
      
      while(isRunning) {
         
         //Framesberechnen ab hier
         long thisFrame = System.currentTimeMillis();
         float timeSinceLastFrame = ((float)(thisFrame-lastFrame))/1000f;
         lastFrame = thisFrame;
         
         if (!ourHolder.getSurface().isValid()) continue;
         canvas = ourHolder.lockCanvas();
         canvas.drawRGB(02, 02, 150);
         
         canvas.drawBitmap(spaceBackground.getLook(), 0, (spaceBackground.getY()-spaceBackground.getLook().getHeight())+canvas.getHeight(), null);
         canvas.drawBitmap(spaceBackground.getLook(), 0, ((spaceBackground.getY()-spaceBackground.getLook().getHeight())+canvas.getHeight())-spaceBackground.getLook().getHeight(), null);
         
         canvas.drawBitmap(greenBall.getBitmap(), greenBall.getX(), greenBall.getY(), null);
         
         canvas.drawBitmap(spaceship.getLook(), spaceship.getPosX(), spaceship.getPosY(), null);
         
         Paint textPaint = new Paint();
         textPaint.setARGB(50, 254, 10, 50);
         textPaint.setTextAlign(Align.CENTER);
         textPaint.setTextSize(50);
         canvas.drawText("get Y: "+ spaceBackground.getY(), canvas.getWidth()/2, 200, textPaint);
         canvas.drawText("canvas x&y: "+ canvas.getWidth() + "/"+ canvas.getHeight(), canvas.getWidth()/2, 250, textPaint);
         canvas.drawText("Rect w: "+ breite, canvas.getWidth()/2, 300, textPaint);
         
         ourHolder.unlockCanvasAndPost(canvas);
         
         //updaten der Bitmaps
         spaceBackground.update(timeSinceLastFrame);
         
         try {
            ourThread.sleep(20);
         } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
      }
   }
   
   @Override
   public boolean onTouchEvent(MotionEvent event) {
      if(event.getAction() == MotionEvent.ACTION_DOWN) {
         
         spaceship.handleActionDown((int)event.getX(), (int)event.getY());
         
         spaceship.handleSpaceshipMove((int)event.getX(), (int)event.getY());
         
      }
      if (event.getAction() == MotionEvent.ACTION_MOVE) {
         if (spaceship.isTouched()) {
            spaceship.setPosX((int)event.getX()-(spaceship.getLook().getWidth()/2));
            spaceship.setPosY((int)event.getY());
         }
         
         if (spaceship.isTouchedToMove()) {
            spaceship.setPosX((int)event.getX()-(spaceship.getLook().getWidth()/2));
         }
      }
      if (event.getAction() == MotionEvent.ACTION_UP) {
         if(spaceship.isTouched()) {
            spaceship.setTouched(false);
         }
         
         if(spaceship.isTouchedToMove()) {
            spaceship.setTouchedToMove(false);
         }
      }   
      
      return true;
   }
}

Immer wenn ich die breite ermitteln will, stürzt die App ab.

Tu’ dir einen Gefallen und lern mit dem Logcat umgehen. Dort wird dir ziemlich genau (Zeilengenau) gesagt wo die App stirbt und welche Exception fliegt. (Meistens jedenfalls.)

Was mach ich denn falsch?

Du vermischt Spiellogik mit Zeichenlogik. Deine View ist so breit wie das sichtbare Feld. Das Canvas ist beliebig groß. Im Normalfall macht man das in Android so, dass man auf ein Bitmap zeichnet und dieses dann auf den jeweiligen Schirm skaliert.

Die Logik des Spiels sollte sowieso in einem eigenen Thread laufen und über Zustände nach außen kommunizieren was gerade zu zeichnen ist. Der Zeichenthread holt sich periodisch diese Zustände ab und zeichnet sie.

Ein gutes Buch zu dem Thema: Beginning Android Games. Hier gibt’s den ganzen Source dazu.

Hallo Schlingel,

also erst mal Danke fürs antworten, aber so ganz werde ich nicht schlau daraus.

ich verstehe nicht, warum ich in der run() Methode die Breite des Canvas ermitteln kann und im Konstruktor halt nicht. Kann es sein, das das Canvas noch nicht gleich “Valid” ist und ich das erst abfangen muss? Wie in der Schleife in der Run Methode?
Es funktioniert ja in der run() Methode optimal, es werden mir sowohl auf dem Handy als auch auf dem Emulator die jeweiligen Breiten angezeigt. Woher weiß den Java, wo ich meine Spiellogik und meine Zeichenlogik ist?!?
Stimmt schon, das mit LogCat muss man sich mal angewöhnen, aber ich spiele ja eigentlich nur etwas rum

Die Logik des Spiels sollte sowieso in einem eigenen Thread laufen und über Zustände nach außen kommunizieren was gerade zu zeichnen ist. Der Zeichenthread holt sich periodisch diese Zustände ab und zeichnet sie.

in der Run Methode wird doch das Canvas gezeichnet, meinst Du, weil ich die Bitmaps in dieser Klasse erstelle? Das hatte ich vorher in der Activity-Klasse aber dann hatte ich das alles in die Thread Klasse gepackt, weil ich halt irgendwie an die Breite des Canvas kommen wollte. Die wollte ich dann mit in den Konstruktor der einzelnen Objekte geben, um halt besser zu kontrollieren, dass sie nicht “raus fliegen”
Ich hatte das bereits mal in einem anderen Tutorial so gemacht, aber das hat irgendwie nicht so richtig funktioniert

Ein gutes Buch zu dem Thema: Beginning Android Games. Hier gibt’s den ganzen Source dazu.

Vielen Dank, schau ich mal rein

Kann es sein, das das Canvas noch nicht gleich „Valid“ ist und ich das erst abfangen muss?

So kann man das sagen. Der Lifecycle ist allerdings nicht an das Canvas sondern an die View in der es enthalten ist gebunden. Sobald die View das erste Mal angezeigt werden soll ermittelt sie ihre Größe. Das kann zwar von Anfang an fest stehen, wenn du z.B. 120sp als View-Breite verwendest, kann allerdings auch von der Display-Breite abhängen wenn du z.B. 120dp oder auch match_parent als Breite verwendest.

Es funktioniert ja in der run() Methode optimal, es werden mir sowohl auf dem Handy als auch auf dem Emulator die jeweiligen Breiten angezeigt.

Vermutlich weil deine Logik in einem onCreate angestoßen wird. Da ist die Verzögerung bis die Views angezeigt werden anscheinend klein genug.

Woher weiß den Java, wo ich meine Spiellogik und meine Zeichenlogik ist?!?

Das ist dem System völlig egal. Aber prinzipiell ist es eher so, dass man die Logik völlig unabhängig vom Zeichnen hält. Dafür misst man die Zeit zwischen solchen Ticks damit das Spiel weiß ob Objekt A von Geschoss B bereits getroffen wurde oder nicht. In deiner Zeichenschleife versucht du nur deine FPS, nehmen wir einfach mal 60 an, zu halten. Und zeichnet stur was gerade zum zeichnen ist. Kollisionserkennung wie z.B. das Verhindern dass das Raumschiff aus dem Zielbereich fliegt sollte da nicht passieren.

Quaxlis Spieletutorial erklärt solche Dinge sehr gut. Vielleicht schaust du dir das mal an bevor du zu Android direkt gehst.

in der Run Methode wird doch das Canvas gezeichnet, meinst Du, weil ich die Bitmaps in dieser Klasse erstelle? Das hatte ich vorher in der Activity-Klasse aber dann hatte ich das alles in die Thread Klasse gepackt, weil ich halt irgendwie an die Breite des Canvas kommen wollte.

Android hat viele, viele verschiedene Display-Größen. Deswegen wurde bereits 2009 auf der Google IO empfohlen dem Bitmap auf dem man zeichnet eine fixe Größe zu geben. Darauf zeichnet man und wenn man das Bild fertig hat skaliert man das Bitmap auf die passende Größe auf dem aktuellen Screen.

Beginning Android Games geht einen etwas anderen Weg und empfiehlt, dass man auch logische Größen verwendet. Diese skaliert man dann entsprechend des aktuellen Displays und zeigt das dann an.

Die wollte ich dann mit in den Konstruktor der einzelnen Objekte geben, um halt besser zu kontrollieren, dass sie nicht „raus fliegen“

Wie gesagt. Nimm lieber eine logische Größe die du skalierst anstatt die physische Größe. Wenn man deine Idee weiterverfolgt dass das Display die Größe der sichtbaren Welt vorgibt wird man sehr bald fest stellen, dass Tablet-Besitzer einen großen Vorteil gegenüber von Besitzern mit kleinen Displays haben :wink:

Außerdem sparst du dir so solche Lifecycle-Probleme.

Stimmt schon, das mit LogCat muss man sich mal angewöhnen, aber ich spiele ja eigentlich nur etwas rum

Wenn ich Tennisspielen geh’, spiel’ ich trotzdem mit einem Schläger :wink:

PS: Ich kann dir die Google-IO Videos zum Thema Spieleentwicklung nur empfehlen.