This lab introduces some of the main ideas necessary for writing real-time apps such as 2D video games (3D Android games normally used OpenGL ES, and are beyond the scope of this course.)
Real-time apps usually involve two threads: the User-Interface (UI) thread which performs the event handling (including the on-touch events to handle user interactions, for example), and a separate app-specific thread to handle the app updating: we call the the app thread. This differs from the WhiteOut puzzle we studied previously which was entirely UI event driven (apart from the Splash Screen which used an AsyncTask).
As before, it is a good idea to separate the model from the view, so typically the app thread will run a loop which repeatecly updates the model, redraws the view, then sleeps for a specified time interval until it is time for the next update / redraw cycle.
But wait! Have you spotted a potential problem already? Remember that only the UI thread can directly update a view, so how can we get around this? There are two main solutions:
- Instead of redrawing the view directly, make a call to the view's postInvalidate method instead. This will then trigger the UI thread to redraw the view as soon as it chooses to.
- Have the app view sub-class SurfaceView. SurfaceView is a special type of view that, providing the app thread holds a lock on, is allowed to redraw it directly.
The first method is the simplest and is the one we choose for this lab. The only disadvantage is that the movement can be slightly jerky. Why do you think this is?
The second method would be the method of choice for any app where smooth movement mattered. It is a bit more complex, and we'll cover it later in the course.
We'll study these ideas in the context of a simple game: Space Battle Game. The aim of Space Battle Game is to shoot at the opponent and kill it before time up. To make it slightly interesting, the opponent is an AI agent (MCTS, Genetic Algorithms, etc.) or a random agent. A screenshot is shown below:
The rules are explained here. This page also contain the code of the original model in Java.
But to be able to use it in our Android App, I've simplified it and resulted in the current project on Github, which you should download and work on.
The exercises to work on during this lab will be listed at the end. You can work on them in the order you like. BUT the ones followed by (*) are the ones you should absolutely work on and you should work on them in the listed order.
The above figure shows the structure of this project.It includes 3 packages (game, agents and tools) and MainActivity, GameView and GameThread. They will be introduced below.
The SpaceBattleGameModel contains the main game, which includes two spaceships and fired missiles. The main useful classes are in the package game. This part of code is already included in the code that you've downloaded.Each spaceship is modelled as having a position (pos), a direction (dir) and velocity. Since this is a 2D game, position, direction and velocity are two-dimensional vectors. There is a separate Vector2d class (see below) which handles all the calculations for these. The size is specified as a radius (radius). Each spaceship has a score and a foreground Paint object (paintBlue/paintGreen) for drawing itself.
Question: Having platform-specific graphics-related classes like this in the model is not ideal: can you think of a better way of modelling this?
The agents package includes some agents used by opponent. The current one is a random agent, which unifromly randomly chooses to turn left, right, thrust, or fire a missile. You can include an MCTS or other AI agents.
The tools package includes some useful classes and methods. Note: Not all the methods declared in the tools package are used in this project.
Vector2d is a utility class that's useful for almost any 2D game. For now the main methods of interest are:
- Constructors for creating new vectors.
- Vector addition (add method)
- The update(Rect) method: this applies wrap-around calculations so that the vectors are always mapped to lie inside the Rect.
- A distance method (dist) useful for checking whether a point is within the radius of a ship or missile
The MainActivity class is the entry point for the app and acts as the controller: its fields and methods are shown below.
As also shown on the classes diagram above, MainActivity has references to the view and the model. As is normal, it should override the onCreate method. It also overrides onResume and onPause: it is in these methods that the GameThread is created and killed respectively.
The size of the view is not available until the view has been created and displayed. The model needs to know this since the ships will be initialised in the middle of the screen. The solution I've adopted here is to set this up when the GameThread is running. GameThread is defined as an inner class of MainActivity: this gives it convenient access to all the methods and fields of MainActivity. GameThread extends Thread. It does all it's work in a run method, which loops while running is true.
Question: why does GameThread use a boolean variable to decide when to stop? (a Thread stops when its run method exits). Why do we not simply stop the GameThread directly with a call to stop() ?
The GameView class as provided extends SurfaceView : it overrides onDraw. This method works by calling the getModel method of the controller (the parent Activity). It then iterates over all the avatars (Ship) in the model, and calls the draw method of each one.
Question: Is it necessary to have a separated ShipView ?method(s). You'll add this / these as part of the lab exercise.
The app as supplied in the .zip file moves creates the bubbles and moves them around the screen, but does not actually function as a game.
Fix the following aspects to make a simple but functional game:
- Set up a Github project for your own game. (*)
- Take a look at the structure of the Ship class, Missile class and SpaceBattleGameModel. (*)
- Find how the score of the game is calculated (hint: getScore() method). Edit it if you want. (*)
- In the game model, the collision is not handled yet. While a ship is hitted by a missile, its healthpoint should reduce by one or more. (*)
- Implement event handling so that the user can control the ship. (*)
- Make the size of the ship relative to the screen size or resolution (currently they are fixed in units of pixels) (*)
- Draw the score and time remaining at suitable points on the screen (*)
- Make the size of the score relative to the screen size or resolution (currently they are fixed in units of pixels) (*)
- To draw the two spaceships, can you use a bitmap (of a plane or spaceship, etc) instead of drawing by Path ?
- Currently the game lasts for a long time (how long?). Make each game last for 1 minute
- Implement some notion of game state: detect when the game is over, and prompt the user to start a new game
- Keep track of the high score and save it in a file
- Make the game full screen and fix the orientation (either portrait or landscape, whichever you think is best)
- Improve the GUI
- Experiment with speed of the ship and missile, and the sizes in order to make the game more fun.
As an optional additional exercise (more difficult): let the user draw there own ship :)
Have fun with the game !!!