14.06.2011

Android Game Development Tutorial – part IV

posted by Karsten

previous

In this part of the tutorial we’ll be looking at the GameView class, which is maintaining the View of the Game, UI and handling messages from the Thread (e.g. score changes).

Create a new class through the Eclipse add class functionality (Right click the project -> add -> class)

You should create a Class with the name GameView that has the superclass android.view.SurfaceView and make it implement SurfaceHolder.Callback (This might create some errors, but we will implement the missing methods later):

public class GameView extends SurfaceView implements SurfaceHolder.Callback

We extend SurfaceView because that is the view that provides a drawable surface, and by implementing SurfaceHolder.Callback the Android system can call our class whenever there are changes to the screen. We need the following attributes:

private volatile GameThread thread;  	private SensorEventListener sensorAccelerometer;  	//Handle communication from the GameThread to the View/Activity Thread 	private Handler mHandler; 	 	//Pointers to the views 	private TextView mScoreView; 	private TextView mStatusView;

The views comes from the layout, and gives us a way to show text on the screen on top of the actual game. The Handler is the class that will recieve messages from the GameThread, and the sensorAccelerometer is an EventHandler that will handle calls from the Accelerometer. Lets create the constructor:

public GameView(Context context, AttributeSet attrs) { 		super(context, attrs);  		//Get the holder of the screen and register interest 		SurfaceHolder holder = getHolder(); 		holder.addCallback(this); 		 		//Set up a handler for messages from GameThread 		mHandler = new Handler() { 			@Override 			public void handleMessage(Message m) { 				if(m.getData().getBoolean("score")) { 					mScoreView.setText(m.getData().getString("text")); 				} 				else {		 					//So it is a status 					mStatusView.setVisibility(m.getData().getInt("viz")); 					mStatusView.setText(m.getData().getString("text")); 				}  			} 		}; 	}

All this does is setting up the holder and add the object as a SurfaceView.Holder callback. The rest of the code creates an anonymous class that handle the messages from the GameThread. It can receive scores and otherwise just display the sent text in the mStatusView. You might remember that the onDestroy() method of the GameActivity would call a cleanup() method of GameView, which should look like this:

//Used to release any resources. 	public void cleanup() { 		this.thread.setRunning(false); 		this.thread.cleanup(); 		 		this.removeCallbacks(thread); 		thread = null; 		 		this.setOnTouchListener(null); 		sensorAccelerometer = null; 		 		SurfaceHolder holder = getHolder(); 		holder.removeCallback(this); 	}

This cleans up all possible resources of the Class. The  method actually has a small risk of a NullException, albeit very small, as the thread is call without a check to see if it has been set up. Should probably be changed 😉

The next methods are the getters and setters. There is only one of them with any real interest:

public void setThread(GameThread newThread) {  		thread = newThread;  		setOnTouchListener(new View.OnTouchListener() {  			public boolean onTouch(View v, MotionEvent event) { 				if(thread!=null) { 					return thread.onTouch(event); 				} 				else return false; 			}  		});  		this.sensorAccelerometer = new SensorEventListener() {  			public void onAccuracyChanged(Sensor arg0, int arg1) { 				// not needed 			}  			public void onSensorChanged(SensorEvent event) { 				if(thread!=null) { 					if (thread.isAlive()) { 						thread.onSensorChanged(event); 					} 				} 			} 		};  		setClickable(true); 		setFocusable(true); 	}

We start out setting the GameThread, and after that we set up the onTouchListener and SensorEventListener and all we do is to relay the events to the GameThread. The rest of the getters and setters look like this:

public GameThread getThread() { 		return thread; 	}  	public TextView getStatusView() { 		return mStatusView; 	}  	public void setStatusView(TextView mStatusView) { 		this.mStatusView = mStatusView; 	} 	 	public TextView getScoreView() { 		return mScoreView; 	}  	public void setScoreView(TextView mScoreView) { 		this.mScoreView = mScoreView; 	} 	  	public Handler getmHandler() { 		return mHandler; 	}  	public void setmHandler(Handler mHandler) { 		this.mHandler = mHandler; 	}

Next we’ll manage the screen changes. First we’ll automatically pause the game if the window goes out of focus for what ever reason:

@Override 	public void onWindowFocusChanged(boolean hasWindowFocus) { 		if(thread!=null) { 			if (!hasWindowFocus) 				thread.pause(); 		} 	}

Next we’ll implement the changes of the surface. Let’s start with the creation of  the surface:

public void surfaceCreated(SurfaceHolder holder) { 		if(thread!=null) { 			thread.setRunning(true); 			 			if(thread.getState() == Thread.State.NEW){ 				//Just start the new thread 				thread.start(); 			} 			else { 				if(thread.getState() == Thread.State.TERMINATED){ 					//Set up and start a new thread with the old thread as seed 					thread = new GameThread(this, thread); 					thread.setRunning(true); 					thread.start(); 				} 			} 		} 	}

The thread.setRunning(true); method call is one we’ll implement, which set the game circle of the thread to running. Next we check to see if the GameThread  is new, and therefore hasn’t run before, or if the GameThread has run before but the user has returned to the game. If it is a new game a simple start of the thread is necessary, however if the player returns to the game from somewhere else it isn’t that simple. Stopping and restarting of threads within Android is deprecated, so the developer has to maintain this manually. The easiest way to do this is to take the old terminated thread, and send it to a new GameThread and copy all of the attributes of the old thread into the new, thus starting the new Thread within the same state as the old thread. This should ensure that the user experience is correct, and the game restarts where the user stopped.

The other two surface related methods are:

//Always called once after surfaceCreated. Tell the GameThread the actual size 	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 		if(thread!=null) { 			thread.setSurfaceSize(width, height);			 		} 	}  	/* 	 * Need to stop the GameThread if the surface is destroyed 	 * Remember this doesn't need to happen when app is paused on even stopped. 	 */ 	public void surfaceDestroyed(SurfaceHolder arg0) { 		 		boolean retry = true; 		if(thread!=null) { 			thread.setRunning(false); 		} 		 		//join the thread with this thread 		while (retry) { 			try { 				if(thread!=null) { 					thread.join(); 				} 				retry = false; 			}  			catch (InterruptedException e) { 				//naugthy, ought to do something... 			} 		} 	}

The surfaceChanged() simply sends the new width and height to the thread. This should happen once and only once for each surface (we don’t allow screen “flipping” here). The surfaceDestroyed() method stops the GameThread by letting it run out of the game loop within the GameThread.

The last thing we need is the two sensor methods we called earlier:

/* 	 * Accelerometer 	 */  	public void startSensor(SensorManager sm) { 		sm.registerListener(this.sensorAccelerometer,  				sm.getDefaultSensor(Sensor.TYPE_ORIENTATION),	 				SensorManager.SENSOR_DELAY_GAME); 	} 	 	public void removeSensor(SensorManager sm) { 		sm.unregisterListener(this.sensorAccelerometer); 		this.sensorAccelerometer = null; 	}

The start sensor sets up the accelerometer. You can use this approach to set up many different sensors, you can go here to see the different possibilities.

Please download GameView.java, if you need it.

next

Share

Place your comment

Please fill your data and comment below.
Name
Email
Website
Your comment