You are new to this series? Please start with the first part.
In our last part of this series, I will present a mini “game” based on the very famous game “Rock, Paper, Scissors”.
The follow features are fully implemented:
- Add a rock, paper or scissors by touching the screen
- Define speed and direction by touch-drag-release
- On collision the loser explodes
- There’s a sound on explosion
This little program has 4 classes which are one activity, one view, one thread and a class representing our bitmaps. I created a class diagram for a easier overview.

The names of class variables, methods and return types should give you the possibility to understand how this game is working. We will now go through every class and explain the most important things.
First lets take a look at our activity class named RockScissorsPaper.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package com.droidnova.android.rockscissorspaper; import android.app.Activity; import android.os.Bundle; import android.view.Window; /** * Activity which will be used as main entry point for the application. * * @author martin */ public class RockScissorsPaper extends Activity { /** * Method called on application start. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(new Panel(this)); } } |
The only important lines are 20 and 21.
On line 20 we request that our window doesn’t have a title because we don’t need a title here.
On line 21 we set our content with a new instance of our panel class so that everything we see will be handled within this class.
Lets start with the Graphic class. Firstly, we should know what we draw before we discuss how we draw.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | package com.droidnova.android.rockscissorspaper; import android.graphics.Bitmap; /** * Class which contains a object we want to draw on specific position. * * @author martin */ public class Graphic { /** * Class which represent the speed the object has in both x and y direction. * * @author martin */ public class Speed { private int _x = 1; private int _y = 1; /** * @return The speed in x direction. Negative amount means, the speed is * backward. */ public int getX() { return _x; } /** * @param speed Speed in x direction. Negative amount means, the speed * is backward. */ public void setX(int speed) { _x = speed; } /** * @return The speed in y direction. Negative amount means, the speed is * backward. */ public int getY() { return _y; } /** * @param speed Speed in y direction. Negative amount means, the speed * is backward. */ public void setY(int speed) { _y = speed; } /** * Helper method. Useful for debugging. */ public String toString() { return "Speed: x: " + _x + " | y: " + _y; } } /** * Contains the coordinates of the instance. * * @author martin */ public class Coordinates { private int _x = 0; private int _y = 0; /** * @return The x coordinate of the upper left corner. */ public int getX() { return _x; } /** * @return The x coordinate of the center. */ public int getTouchedX() { return _x + _bitmap.getWidth() / 2; } /** * @param value The new x coordinate of the upper left corner. */ public void setX(int value) { _x = value; } /** * @param value The new x coordinate of the center. */ public void setTouchedX(int value) { _x = value - _bitmap.getWidth() / 2; } /** * @return The y coordinate of the upper left corner. */ public int getY() { return _y; } /** * @return The y coordinate of the center. */ public int getTouchedY() { return _y + _bitmap.getHeight() / 2; } /** * @param value The new y coordinate of the upper left corner. */ public void setY(int value) { _y = value; } /** * @param value The new y coordinate of the center. */ public void setTouchedY(int value) { _y = value - _bitmap.getHeight() / 2; } /** * Helper method for debugging. */ public String toString() { return "Coordinates: (" + _x + "/" + _y + ")"; } } /** * Bitmap which should be drawn. */ private Bitmap _bitmap; /** * Coordinates on which the bitmap should be drawn. */ private Coordinates _coordinates; /** * Speed of the object. */ private Speed _speed; /** * Object type which could be rock, scissors, paper or explosion. */ private String _type; /** * Step of explosion which will take 50 steps. */ private int _explosionStep = 0; /** * Constructor. * * @param bitmap Bitmap which should be drawn. */ public Graphic(Bitmap bitmap) { _bitmap = bitmap; _coordinates = new Coordinates(); _speed = new Speed(); } /** * @param bitmap New bitmap to draw. */ public void setBitmap(Bitmap bitmap) { _bitmap = bitmap; } /** * @return The stored bitmap. */ public Bitmap getBitmap() { return _bitmap; } /** * @return The speed of the instance */ public Speed getSpeed() { return _speed; } /** * @return The coordinates of the instance. */ public Coordinates getCoordinates() { return _coordinates; } /** * @param type The new type of the instance. */ public void setType(String type) { _type = type; } /** * @return The type of the instance. */ public String getType() { return _type; } /** * @param step The new explosion step. */ public void setExplosionStep(int step) { _explosionStep = step; } /** * @return The explosion step. */ public int getExplosionStep() { return _explosionStep; } } |
The Graphic class saves some information.
The first one is the speed the bitmap has. Positive values of x and y indicate the direction is towards the right (in case of x) or towards the bottom (in case of y). Negative values mean the opposing direction. The amount itself indicates how many pixels the bitmap moves between game updates. For the speed we have only getter and setter methods.
The second one are the coordinates at which the graphic should be drawn if a draw is processed. We have the coordinates in two int values, one for x and one for y. We also have getter and setter here but with one little difference. We know that the coordinates for the bitmap always match the upper left corner. If we simply use the touch coordinates to set the coordinates of the bitmap, our bitmap wouldn’t appear centered under our touch. We always would have to deal with the offset and the user would be confused. To prevent that, we have 4 other getter and setter which are named like setTouchedX() or getTouchedX(). Inside these methods we do the math to calculate the correct coordinates of the upper left corner relative to our touch coordinates.
The last information the Graphic class stores are the bitmap itself, the type of the bitmap (e.g. if it is a rock or a explosion), and the explosion step. The explosion itself will be processed in 50 steps and here we know in which step we are.
The game loop was implemented in a thread. The class named RockScissorsPaperThread.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | package com.droidnova.android.rockscissorspaper; import android.graphics.Canvas; /** * Thread class to perform the so called "game loop". * * @author martin */ class RockScissorsPaperThread extends Thread { private Panel _panel; private boolean _run = false; /** * Constructor. * * @param panel View class on which we trigger the drawing. */ public RockScissorsPaperThread(Panel panel) { _panel = panel; } /** * @param run Should the game loop keep running? */ public void setRunning(boolean run) { _run = run; } /** * @return If the game loop is running or not. */ public boolean isRunning() { return _run; } /** * Perform the game loop. * Order of performing: * 1. update physics * 2. check for winners * 3. draw everything */ @Override public void run() { Canvas c; while (_run) { c = null; try { c = _panel.getHolder().lockCanvas(null); synchronized (_panel.getHolder()) { _panel.updatePhysics(); _panel.checkForWinners(); _panel.onDraw(c); } } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { _panel.getHolder().unlockCanvasAndPost(c); } } } } } |
The implementation is pretty simple. The constructor has the panel it should control as a parameter. We have two methods, one for setting the running status to stop our game loop and one to ask, if the thread is running or not.
The method run() is the heart of our thread because it represents the game loop. We get the canvas by locking the canvas for drawing (line 50), synchronize with the SurfaceHolder of our panel and start updating the physics, check for winners and finally we draw everything.
The primary class, because it does nearly everything, is our Panel. This class is 400 lines long but full commented, thats why I just post the interesting stuff here and explain it.
We start with the constructor.
1 2 3 4 5 6 7 8 9 10 11 12 13 | /** * Constructor called on instantiation. * @param context Context of calling activity. */ public Panel(Context context) { super(context); fillBitmapCache(); _soundPool = new SoundPool(16, AudioManager.STREAM_MUSIC, 100); _playbackFile = _soundPool.load(getContext(), R.raw.explosion, 0); getHolder().addCallback(this); _thread = new RockScissorsPaperThread(this); setFocusable(true); } |
Two important things you should see here. We create a SoundPool instance (line
and we load a sound file to play it later (line 9).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | /** * Update the physics of each item already added to the panel. * Not including items which are currently exploding and moved by a touch event. */ public void updatePhysics() { Graphic.Coordinates coord; Graphic.Speed speed; for (Graphic graphic : _graphics) { coord = graphic.getCoordinates(); speed = graphic.getSpeed(); // Direction coord.setX(coord.getX() + speed.getX()); coord.setY(coord.getY() + speed.getY()); // borders for x... if (coord.getX() < 0) { speed.setX(-speed.getX()); coord.setX(-coord.getX()); } else if (coord.getX() + graphic.getBitmap().getWidth() > getWidth()) { speed.setX(-speed.getX()); coord.setX(coord.getX() + getWidth() - (coord.getX() + graphic.getBitmap().getWidth())); } // borders for y... if (coord.getY() < 0) { speed.setY(-speed.getY()); coord.setY(-coord.getY()); } else if (coord.getY() + graphic.getBitmap().getHeight() > getHeight()) { speed.setY(-speed.getY()); coord.setY(coord.getY() + getHeight() - (coord.getY() + graphic.getBitmap().getHeight())); } } } |
Here we calculate the collisions with the borders and update the position of every bitmap on the screen. It’s plain math and should be easy to understand.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * Check all items on the panel for collisions and find the winner. * The loser will added to the list of explosions. */ public void checkForWinners() { ArrayList<Graphic> toExplosion = new ArrayList<Graphic>(); for (Graphic grapics : _graphics) { for (Graphic battleGraphic : _graphics) { if (battleGraphic != grapics && !(toExplosion.contains(battleGraphic) || toExplosion.contains(grapics))) { if (!battleGraphic.getType().equals(grapics.getType()) && checkCollision(battleGraphic, grapics)) { if (firstWins(battleGraphic.getType(), grapics.getType())) { toExplosion.add(grapics); _soundPool.play(_playbackFile, 1, 1, 0, 0, 1); } } } else { continue; } } } if (!toExplosion.isEmpty()) { _explosions.addAll(toExplosion); _graphics.removeAll(toExplosion); } } |
How do we check for winners? We do that while iterating two times and check if they do collide or not and later if one of them beat the other one. If that happens, we play our explosion sound initialized in the constructor. Finally we remove all beaten bitmaps and add them to our list of bitmaps, which should start and process the explosion.
Our onDraw() method is a bit more complex.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | /** * Draw on the SurfaceView. * Order: * <ul> * <li>Background image</li> * <li>Items on the panel</li> * <li>Explosions</li> * <li>Item moved by hand</li> * </ul> */ @Override public void onDraw(Canvas canvas) { // draw the background canvas.drawBitmap(_bitmapCache.get(R.drawable.abstrakt), 0, 0, null); Bitmap bitmap; Graphic.Coordinates coords; // draw the normal items for (Graphic graphic : _graphics) { bitmap = graphic.getBitmap(); coords = graphic.getCoordinates(); canvas.drawBitmap(bitmap, coords.getX(), coords.getY(), null); } // draw the explosions ArrayList<Graphic> finishedExplosion = new ArrayList<Graphic>(); for (Graphic graphic : _explosions) { if (!graphic.getType().equals("explosion")) { graphic.setType("explosion"); graphic.setExplosionStep(0); graphic.getSpeed().setX(0); graphic.getSpeed().setY(0); graphic.setBitmap(_bitmapCache.get(R.drawable.smaller)); bitmap = graphic.getBitmap(); coords = graphic.getCoordinates(); canvas.drawBitmap(bitmap, coords.getX(), coords.getY(), null); } else { switch (graphic.getExplosionStep()) { case 10: bitmap = _bitmapCache.get(R.drawable.small); graphic.setBitmap(bitmap); break; case 20: bitmap = _bitmapCache.get(R.drawable.big); graphic.setBitmap(bitmap); break; case 30: bitmap = _bitmapCache.get(R.drawable.small); graphic.setBitmap(bitmap); break; case 40: bitmap = _bitmapCache.get(R.drawable.smaller); graphic.setBitmap(bitmap); break; default: bitmap = graphic.getBitmap(); } coords = graphic.getCoordinates(); canvas.drawBitmap(bitmap, coords.getX(), coords.getY(), null); graphic.setExplosionStep(graphic.getExplosionStep() + 1); } if (graphic.getExplosionStep() > 50) { finishedExplosion.add(graphic); } } // remove all Objects who are already fully exploded... if (!finishedExplosion.isEmpty()) { _explosions.removeAll(finishedExplosion); } // draw current graphic at last... if (_currentGraphic != null) { bitmap = _currentGraphic.getBitmap(); coords = _currentGraphic.getCoordinates(); canvas.drawBitmap(bitmap, coords.getX(), coords.getY(), null); } } |
The first thing we draw is out background image. Then we iterate over our normal bitmaps and draw them. Nothing new here.
The second step is the processing of the explosions. First we check if the explosion was already initialized or not. If not, we initialize it and set the type of the bitmap to explosion. If it is so, we do the 50 steps to process the explosion and draw it.
Finally we remove every fully exploded bitmap and draw the current graphic, the one which follow our touch event, on the screen.
Thats all.
You should be able to modify the “game”, to add a bounce effect on collision of equal bitmaps. You learned a bit about collision detection, game loops and how to draw on a SurfaceView.
I hope you enjoyed it! Feel free to post your feedback!
Full source (complete Eclipse project): RockScissorsPaper


Comments
Leave a comment Trackback