SurfaceView problem beim zeichnen von Sprite


#1

Ich will in die 2D Spielprogrammierung einsteigen, ich verwende dabei eine SurfaceView auf der ich ein Sprite zeichnen möchte. Ich bekomme aber eine NPE und weiss nicht so genau wo ich etwas falsch gemacht habe. Hier der Stacktrace:

02-21 17:52:24.309: E/AndroidRuntime(21430): FATAL EXCEPTION: main
02-21 17:52:24.309: E/AndroidRuntime(21430): java.lang.NullPointerException
02-21 17:52:24.309: E/AndroidRuntime(21430): at com.example.canvastest.GameView.draw(GameView.java:36)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.getDisplayList(View.java:13020)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.getDisplayList(View.java:13062)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.draw(View.java:13839)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewGroup.drawChild(ViewGroup.java:3086)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2923)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.getDisplayList(View.java:13015)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.getDisplayList(View.java:13062)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.draw(View.java:13839)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewGroup.drawChild(ViewGroup.java:3086)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2923)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.getDisplayList(View.java:13015)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.getDisplayList(View.java:13062)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.draw(View.java:13839)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewGroup.drawChild(ViewGroup.java:3086)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2923)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.getDisplayList(View.java:13015)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.getDisplayList(View.java:13062)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.draw(View.java:13839)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewGroup.drawChild(ViewGroup.java:3086)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2923)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.draw(View.java:14129)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.widget.FrameLayout.draw(FrameLayout.java:471)
02-21 17:52:24.309: E/AndroidRuntime(21430): at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2231)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.getDisplayList(View.java:13020)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.View.getDisplayList(View.java:13062)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.HardwareRenderer$GlRenderer.buildDisplayList(HardwareRenderer.java:1411)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:1359)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewRootImpl.draw(ViewRootImpl.java:2375)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2247)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1880)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1008)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5508)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.Choreographer.doCallbacks(Choreographer.java:562)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.Choreographer.doFrame(Choreographer.java:532)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.os.Handler.handleCallback(Handler.java:730)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.os.Handler.dispatchMessage(Handler.java:92)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.os.Looper.loop(Looper.java:213)
02-21 17:52:24.309: E/AndroidRuntime(21430): at android.app.ActivityThread.main(ActivityThread.java:5225)
02-21 17:52:24.309: E/AndroidRuntime(21430): at java.lang.reflect.Method.invokeNative(Native Method)
02-21 17:52:24.309: E/AndroidRuntime(21430): at java.lang.reflect.Method.invoke(Method.java:525)
02-21 17:52:24.309: E/AndroidRuntime(21430): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:741)
02-21 17:52:24.309: E/AndroidRuntime(21430): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
02-21 17:52:24.309: E/AndroidRuntime(21430): at dalvik.system.NativeStart.main(Native Method)

und hier mein bisheriger code:

[XML]

<com.example.canvastest.GameView
    android:id="@+id/surfaceViewGame"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#80000000" >
</com.example.canvastest.GameView>

<LinearLayout
    android:id="@+id/linearLayoutButtons"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|right" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="l" >
    </Button>

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="r" >
    </Button>
</LinearLayout>

[/XML]


import android.app.Activity;
import android.os.Bundle;

public class GameActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_game);
	}
}

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class GameView extends SurfaceView implements SurfaceHolder.Callback {

	private Bitmap bmp;
	private GameLoopThread theGameLoopThread;
	private Sprite sprite;

	public GameView(Context context) {
		super(context);
		bmp = BitmapFactory.decodeResource(getResources(), R.drawable.square);
		sprite = new Sprite(bmp);
		theGameLoopThread = new GameLoopThread(this);
	}

	public GameView(Context context, AttributeSet attrSet, int defStyle) {
		super(context, attrSet, defStyle);
	}

	public GameView(Context context, AttributeSet attrSet) {
		super(context, attrSet);
	}

	@Override
	public void draw(Canvas canvas) {
		canvas.drawColor(Color.DKGRAY);
		sprite.draw(canvas);
	}

	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
	}

	public void surfaceCreated(SurfaceHolder holder) {
		theGameLoopThread.setRunning(true);
		theGameLoopThread.start();
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		boolean retry = true;
		theGameLoopThread.setRunning(false);
		while (retry) {
			try {
				theGameLoopThread.join();
				retry = false;
			} catch (InterruptedException e) {

			}
		}
	}

}```

```package com.example.canvastest;

import android.graphics.Canvas;

public class GameLoopThread extends Thread {
	private static final long FPS = 60;
	private GameView gameView;
	private boolean isRunning = false;

	public GameLoopThread(GameView gameView) {
		this.gameView = gameView;
	}

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

	@Override
	public void run() {
		long TPS = 1000 / FPS;
		long startTime, sleepTime;
		while (isRunning) {
			Canvas theCanvas = null;
			startTime = System.currentTimeMillis();
			try {
				theCanvas = gameView.getHolder().lockCanvas();
				synchronized (gameView.getHolder()) {
					gameView.draw(theCanvas);
				}
			} finally {
				if (theCanvas != null) {
					gameView.getHolder().unlockCanvasAndPost(theCanvas);
				}
			}
			sleepTime = TPS - (System.currentTimeMillis() - startTime);
			try {
				if (sleepTime > 0)
					sleep(sleepTime);
				else
					sleep(10);
			} catch (Exception e) {
			}
		}
	}
}

import android.graphics.Bitmap;
import android.graphics.Canvas;

public class Sprite {
	private int x = 0;
	private int y = 0;
	private int xSpeed;
	private int ySpeed;
	private int width;
	private int height;
	private Bitmap bmp;

	public Sprite(Bitmap bmp) {
		this.bmp = bmp;
		this.width = bmp.getWidth();
		this.height = bmp.getHeight();
		ySpeed = 2;
		xSpeed = 2;
	}

	/*
	 * private void bounceOff() { if (x > theGameView.getWidth() - width -
	 * xSpeed || x + xSpeed < 0) { xSpeed = -xSpeed; } x = x + xSpeed; if (y >
	 * theGameView.getHeight() - height - ySpeed || y + ySpeed < 0) { ySpeed =
	 * -ySpeed; } y = y + ySpeed; }
	 */

	public void draw(Canvas canvas) {
		x += xSpeed;
		y += ySpeed;
		canvas.drawBitmap(bmp, x, y, null);
	}
}

Der Fehler tritt gleich nach dem Start der App auf und wie ihr in den ST sehen könnt, bei der Zeile wo ich den Sprite zeichnen möchte.

*** Edit ***

Ich habe ein paar Änderungen in der Klasse GameView vorgenommen und nun zeichnet sich mein Sprite, jedoch wird es nicht update aktualisiert (d.h. der Sprite bleibt bei der selben Position obwohl ich die x und y Werte ändere).
Hier mal der neue Code der Klasse GameView:


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class GameView extends SurfaceView implements SurfaceHolder.Callback {

	private Bitmap bmp;
	private GameLoopThread theGameLoopThread;
	private Sprite sprite;
	private boolean isCreated = false;

	public GameView(Context context) {
		super(context);
		getHolder().addCallback(this);
		theGameLoopThread = new GameLoopThread(this);
	}

	public GameView(Context context, AttributeSet attrSet, int defStyle) {
		super(context, attrSet, defStyle);
	}

	public GameView(Context context, AttributeSet attrSet) {
		super(context, attrSet);
	}

	private void createOneSprite() {
		if (!isCreated) {
			bmp = BitmapFactory.decodeResource(getResources(),
					R.drawable.square);
			sprite = new Sprite(bmp);
			isCreated = true;
		}
	}

	@Override
	public void draw(Canvas canvas) {
		createOneSprite();
		canvas.drawColor(Color.DKGRAY);
		sprite.draw(canvas);
	}

	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
	}

	public void surfaceCreated(SurfaceHolder holder) {
		theGameLoopThread.setRunning(true);
		theGameLoopThread.start();
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		boolean retry = true;
		theGameLoopThread.setRunning(false);
		while (retry) {
			try {
				theGameLoopThread.join();
				retry = false;
			} catch (InterruptedException e) {

			}
		}
	}

}```

#2

Wegen der NPE: Dem solltest du nachgehen.


#3

Nachdem ich die Änderungen an der GameView Klasse vorgenommen habe ist der Fehler mit der NPE gelöst, man muss anscheinend den Sprite in der draw Methode instanzieren. Nun ist mein Problem eben, dass das Sprite sich nicht bewegt obwohl ich bei jedem Threaddurchlauf die x und y Werte des Sprites ändere.


#4

Ziemlich viel code zum Drüberlesen. Es wäre auch ganz gut, wenn du sagen würdest, WAS denn die Änderung war, nach der es jetzt (“besser”) funktioniert.

Zur neuen Frage: Was gibt er denn bei

    public void draw(Canvas canvas) {
        x += xSpeed;
        y += ySpeed;
 
        System.out.println("Drawing at "+x+" "+y); // Oder log...
        canvas.drawBitmap(bmp, x, y, null);
    }

aus?


#5

[QUOTE=Marco13]Ziemlich viel code zum Drüberlesen. Es wäre auch ganz gut, wenn du sagen würdest, WAS denn die Änderung war, nach der es jetzt (“besser”) funktioniert.

Zur neuen Frage: Was gibt er denn bei

    public void draw(Canvas canvas) {
        x += xSpeed;
        y += ySpeed;
 
        System.out.println("Drawing at "+x+" "+y); // Oder log...
        canvas.drawBitmap(bmp, x, y, null);
    }

aus?[/QUOTE]

Ich habe eine neue Methode createOneSprite geschrieben die ich dann in der draw Methode der Klasse GameView ausführe.
Ich habe jetzt die x und y Werte ausgegeben und seltsamerweise geht es nur bis zu 4, dann tut sich nichts mehr.

*** Edit ***

Ich bin jetzt dem Problem ein ganzes Stückchen näher gerückt, anscheinend wird die methode surfaceCreated() nie ausgeführt und somit wird der Thread auch nie gestartet.

*** Edit ***

Ok ich habe nun alles zum starten gebracht, das Sprite bewegt sich. Ich musste die GameActivity wie folgt ändern:


import android.app.Activity;
import android.os.Bundle;

public class GameActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		GameView gameView = new GameView(this);
		setContentView(gameView);
	}
}

ich habe jetzt aber ein anderes Problem, die Buttons die ich in der XML Datei deklariert habe werden nämlich nicht angezeigt (was mir auch logisch erscheint, da ich jetzt die GameView als contentView setze und nicht mehr die Layout Datei). Weiss jemand wie ich buttons hinzufügen kann ohne diese zu zeichnen?