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


First some pictures:
stone-scissor-paper

stone-scissor-paper

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.
RockScissorsPaper class diagram

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 8) 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

Share