Coding a Snake Game for Android
Admin
This game will use a different engine to the other games on this site as it will make a pre-determined number of “moves” each second, rather than playing as many frames of animation as possible and then timing each frame for maximum smoothness. The reason for this is we can recreate a authentic blocky/jumpy animation.
One dot for the snake and one dot for Bob waiting to be eaten. Before anyone complains that snakes don’t eat Bobs, they don’t eat apples either. As the game continues and many Bobs are eaten the snake grows in length making it more likely that the player will trap or eat himself.
Let’s start coding.
Coding the Snake Activity
As usual, we will start with an Activity which will control a thread in a class which controls the game and handles input from the player. If you want a more in-depth discussion of the interaction between the Activity and the main class then take a look at the Breakout tutorial.
Create a new project in Android Studio, use the Empty Activity template, and call it Snake. Leave the rest of the settings at their defaults. Call the Activity SnakeActivity and amend its code to be the same as this.
One dot for the snake and one dot for Bob waiting to be eaten. Before anyone complains that snakes don’t eat Bobs, they don’t eat apples either. As the game continues and many Bobs are eaten the snake grows in length making it more likely that the player will trap or eat himself.
import android.app.Activity;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
public class SnakeActivity extends Activity {
// Declare an instance of SnakeEngine
// We will code this soon
SnakeEngine snakeEngine;
}
Here we declare an instance of SnakeEngine called snakeEngine which doesn’t exist yet but it will soon. Now code the onCreate method of the SnakeActivity class to initialize the SnakeEngine object. Obviously there will be errors in our code but if we code SnakeActivity in full we won’t need to keep coming back to it. Add the following code and we will discuss it.
import android.app.Activity;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
public class SnakeActivity extends Activity {
// Declare an instance of SnakeEngine
SnakeEngine snakeEngine;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get the pixel dimensions of the screen
Display display = getWindowManager().getDefaultDisplay();
// Initialize the result into a Point object
Point size = new Point();
display.getSize(size);
// Create a new instance of the SnakeEngine class
snakeEngine = new SnakeEngine(this, size);
// Make snakeEngine the view of the Activity
setContentView(snakeEngine);
}
}
The onCreate method uses the Display class and an object of type Point to get the resolution of the device the game is running on. Our SnakeEngine class will need a reference to the Activity and the resolution so we pass them in to the SnakeEngine constructor. The last thing we do is use snakeEngine to be the View of the SnakeActivity.
Now we can code the onPause and onResume methods. Add the following code. Again. for a full explanation read the Breakout tutorial but basically Android will call these onPause and onResume methods then these methods call the relevant methods inside SnakeEngine to start and stop the thread which handles the entire game.
// Start the thread in snakeEngine
@Override
protected void onResume() {
super.onResume();
snakeEngine.resume();
}
// Stop the thread in snakeEngine
@Override
protected void onPause() {
super.onPause();
snakeEngine.pause();
}
Making the game fullscreen and landscape
We want to use every pixel that the device has to offer so we will make changes to the app’s AndroidManifest.xml configuration file.
In the project explorer pane in Android Studio double click on the manifests folder, this will open up the AndroidManifest.xml file in the code editor.
In the AndroidManifest.xml file, locate the following line of code: android:name=”.SnakeActivity”>
Place the cursor before the closing > shown above. Tap the enter key a couple of times to move the > a couple of lines below the rest of the line shown above.
Immediately below ParallaxActivity but BEFORE the newly positioned > type or copy and paste these two lines to make the game run full screen and lock it in the landscape orientation.
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape"
1
2
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape"
Add the sound to the project
Download the sounds by right-clicking on the files listed below. Add them to the Snake project by using your operating system’s file browser go to the app\src\main folder of the project and create a new folder called assets. Add your sound files to this folder. Here are my sound effects. Right-click and select Save link as… to download them.
Note: At the moment my Web host seems to be restricting me from uploading .ogg files. Just look at one of my other projects, download the files from them and rename them to suit below. You could also make your own or download the bonus content (above). Sorry will fix this as soon as I can.
snake_crash
eat_bob
We can now get rid of the errors by moving on to the SnakeEngine class.
Coding SnakeEngine
Add a new class called SnakeEngine and amend the code as shown next so we have all the required imports.
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.Point;
import android.media.AudioManager;
import android.media.SoundPool;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
import java.util.Random;
import android.content.res.AssetManager;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
class SnakeEngine extends SurfaceView implements Runnable {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.Point;
import android.media.AudioManager;
import android.media.SoundPool;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
import java.util.Random;
import android.content.res.AssetManager;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
class SnakeEngine extends SurfaceView implements Runnable {
}
When we extend SurfaceView so that the call to setContentView in the SnakeActivity class works and we implement the Runnable interface so we can later pass this class to the Thread constructor to create a Thread instance. Runnable has one method that we must implement and we will overide run soon.
The SnakeEngine variables
Add all the member variables after the class declaration then they will be ready for use as we proceed through the rest of the code.
// Our game thread for the main game loop
private Thread thread = null;
// To hold a reference to the Activity
private Context context;
// for plaing sound effects
private SoundPool soundPool;
private int eat_bob = -1;
private int snake_crash = -1;
// For tracking movement Heading
public enum Heading {UP, RIGHT, DOWN, LEFT}
// Start by heading to the right
private Heading heading = Heading.RIGHT;
// To hold the screen size in pixels
private int screenX;
private int screenY;
// How long is the snake
private int snakeLength;
// Where is Bob hiding?
private int bobX;
private int bobY;
// The size in pixels of a snake segment
private int blockSize;
// The size in segments of the playable area
private final int NUM_BLOCKS_WIDE = 40;
private int numBlocksHigh;
// Control pausing between updates
private long nextFrameTime;
// Update the game 10 times per second
private final long FPS = 10;
// There are 1000 milliseconds in a second
private final long MILLIS_PER_SECOND = 1000;
// We will draw the frame much more often
// How many points does the player have
private int score;
// The location in the grid of all the segments
private int[] snakeXs;
private int[] snakeYs;
// Everything we need for drawing
// Is the game currently playing?
private volatile boolean isPlaying;
// A canvas for our paint
private Canvas canvas;
// Required to use canvas
private SurfaceHolder surfaceHolder;
// Some paint for our canvas
private Paint paint;
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
// Our game thread for the main game loop
private Thread thread = null;
// To hold a reference to the Activity
private Context context;
// for plaing sound effects
private SoundPool soundPool;
private int eat_bob = -1;
private int snake_crash = -1;
// For tracking movement Heading
public enum Heading {UP, RIGHT, DOWN, LEFT}
// Start by heading to the right
private Heading heading = Heading.RIGHT;
// To hold the screen size in pixels
private int screenX;
private int screenY;
// How long is the snake
private int snakeLength;
// Where is Bob hiding?
private int bobX;
private int bobY;
// The size in pixels of a snake segment
private int blockSize;
// The size in segments of the playable area
private final int NUM_BLOCKS_WIDE = 40;
private int numBlocksHigh;
// Control pausing between updates
private long nextFrameTime;
// Update the game 10 times per second
private final long FPS = 10;
// There are 1000 milliseconds in a second
private final long MILLIS_PER_SECOND = 1000;
// We will draw the frame much more often
// How many points does the player have
private int score;
// The location in the grid of all the segments
private int[] snakeXs;
private int[] snakeYs;
// Everything we need for drawing
// Is the game currently playing?
private volatile boolean isPlaying;
// A canvas for our paint
private Canvas canvas;
// Required to use canvas
private SurfaceHolder surfaceHolder;
// Some paint for our canvas
private Paint paint;
We can now code the constructor.
Coding the SnakeEngine constructor
Add this code next, be sure to add it inside the closing curly brace of the SnakeEngine class.
public SnakeEngine(Context context, Point size) {
super(context);
context = context;
screenX = size.x;
screenY = size.y;
// Work out how many pixels each block is
blockSize = screenX / NUM_BLOCKS_WIDE;
// How many blocks of the same size will fit into the height
numBlocksHigh = screenY / blockSize;
// Set the sound up
soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
try {
// Create objects of the 2 required classes
// Use m_Context because this is a reference to the Activity
AssetManager assetManager = context.getAssets();
AssetFileDescriptor descriptor;
// Prepare the two sounds in memory
descriptor = assetManager.openFd("get_mouse_sound.ogg");
eat_bob = soundPool.load(descriptor, 0);
descriptor = assetManager.openFd("death_sound.ogg");
snake_crash = soundPool.load(descriptor, 0);
} catch (IOException e) {
// Error
}
// Initialize the drawing objects
surfaceHolder = getHolder();
paint = new Paint();
// If you score 200 you are rewarded with a crash achievement!
snakeXs = new int[200];
snakeYs = new int[200];
// Start the game
newGame();
}
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
public SnakeEngine(Context context, Point size) {
super(context);
context = context;
screenX = size.x;
screenY = size.y;
// Work out how many pixels each block is
blockSize = screenX / NUM_BLOCKS_WIDE;
// How many blocks of the same size will fit into the height
numBlocksHigh = screenY / blockSize;
// Set the sound up
soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
try {
// Create objects of the 2 required classes
// Use m_Context because this is a reference to the Activity
AssetManager assetManager = context.getAssets();
AssetFileDescriptor descriptor;
// Prepare the two sounds in memory
descriptor = assetManager.openFd("get_mouse_sound.ogg");
eat_bob = soundPool.load(descriptor, 0);
descriptor = assetManager.openFd("death_sound.ogg");
snake_crash = soundPool.load(descriptor, 0);
} catch (IOException e) {
// Error
}
// Initialize the drawing objects
surfaceHolder = getHolder();
paint = new Paint();
// If you score 200 you are rewarded with a crash achievement!
snakeXs = new int[200];
snakeYs = new int[200];
// Start the game
newGame();
}
First, we initialize, context, screenX and screenY with the values passed in from SnakeActivity. Next, we divide the number of pixels by the final int NUM_BLOCKS_WIDE in order to determine the appropriate number of pixels in the width of blockSize. Now we can use this to work out, based on the number of vertical pixels, how many blocks high the playable area will be.
Next, the sound files are loaded and associated with an appropriately named identifier. They are now ready to play at will with soundPool.playSound.
After this, we initialized the two int arrays. snakeXs will hold the horizontal coordinate of each segment of the snake and snakeYs will hold each vertical coordinate.
The last part of the code we call the newGame method which unsurprisingly starts the game. We will code newGame shortly.
Making the thread run the game loop
All the in the run method, including method calls from the run method, works in a separate thread to the Android UI. This will allow our game to run smoothly at the same time as listening for player input. Add the run method as well as pause and resume and then we will talk about them.
@Override
public void run() {
while (isPlaying) {
// Update 10 times a second
if(updateRequired()) {
update();
draw();
}
}
}
public void pause() {
isPlaying = false;
try {
thread.join();
} catch (InterruptedException e) {
// Error
}
}
public void resume() {
isPlaying = true;
thread = new Thread(this);
thread.start();
}
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
@Override
public void run() {
while (isPlaying) {
// Update 10 times a second
if(updateRequired()) {
update();
draw();
}
}
}
public void pause() {
isPlaying = false;
try {
thread.join();
} catch (InterruptedException e) {
// Error
}
}
public void resume() {
isPlaying = true;
thread = new Thread(this);
thread.start();
}
The pause and resume methods are called by SnakeActivity when Android or the player causes the app to call onPause or onResume. The resume method creates a new instance of Thread when required and pause stops the it when required. Now our instance of Thread will play nicely with Android.
Everything in, and called by the run method, will now happen in a separate thread.
The run method calls update and then draw. The whole thing is wrapped in a while loop that repeats continuously if isPlaying is set to true and the thread is running.
These calls are also contained within if(updateRequired()). Only if this is true are the update and draw methods called. The updateRequired method can, therefore, control the frame rate of the game ensureing the blocky/authentic motion.
Some more methods
As we saw, the newGame method is called by the constructor it is also called when the snake crashes and a new game is required. Add the newGame method.
public void newGame() {
// Start with a single snake segment
snakeLength = 1;
snakeXs[0] = NUM_BLOCKS_WIDE / 2;
snakeYs[0] = numBlocksHigh / 2;
// Get Bob ready for dinner
spawnBob();
// Reset the score
score = 0;
// Setup nextFrameTime so an update is triggered
nextFrameTime = System.currentTimeMillis();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void newGame() {
// Start with a single snake segment
snakeLength = 1;
snakeXs[0] = NUM_BLOCKS_WIDE / 2;
snakeYs[0] = numBlocksHigh / 2;
// Get Bob ready for dinner
spawnBob();
// Reset the score
score = 0;
// Setup nextFrameTime so an update is triggered
nextFrameTime = System.currentTimeMillis();
}
In the newGame method, the snake is prepared. The length is set to just one block then the head of the snake is set to the center of the screen. The first position of each of the arrays holds the head. It is only the head that we will use when we code the collision detection. Next, Bob is prepared for a terrible demise by calling spawnBob and score is initialized to .
The final bit of code in the newGame method sets nextFrameTime to whatever the current time is. This will cause the update and draw methods run.
Spawning and eating Bob
The spawnBob method uses two random int values within the ranges of zero and NUM_BLOCKS_WIDE, zero and numBlocksHigh, then initializes the horizontal and vertical location of the mouse.
public void spawnBob() {
Random random = new Random();
bobX = random.nextInt(NUM_BLOCKS_WIDE - 1) + 1;
bobY = random.nextInt(numBlocksHigh - 1) + 1;
}
1
2
3
4
5
public void spawnBob() {
Random random = new Random();
bobX = random.nextInt(NUM_BLOCKS_WIDE - 1) + 1;
bobY = random.nextInt(numBlocksHigh - 1) + 1;
}
Optimization tip: Instantiating a new instance of Random is slow and could be done in the constructor then just reused each time spawnBob is called. In this context, however it will not affect the smooth running of the game.
The eatBob method is simple too.
The snake’s length is increased by one block, a new mouse is spawned, 1 is added to the score and a sound effect is played.
Here is the code for the eatBob method to add after the spawnBob method.
private void eatBob(){
// Got him!
// Increase the size of the snake
snakeLength++;
//replace Bob
// This reminds me of Edge of Tomorrow. Oneday Bob will be ready!
spawnBob();
//add to the score
score = score + 1;
soundPool.play(eat_bob, 1, 1, 0, 0, 1);
}
1
2
3
4
5
6
7
8
9
10
11
private void eatBob(){
// Got him!
// Increase the size of the snake
snakeLength++;
//replace Bob
// This reminds me of Edge of Tomorrow. Oneday Bob will be ready!
spawnBob();
//add to the score
score = score + 1;
soundPool.play(eat_bob, 1, 1, 0, 0, 1);
}
The moveSnake method is quite long but doesn’t involve anything too tricky. Add the code and then we can go through it.
private void moveSnake(){
// Move the body
for (int i = snakeLength; i > 0; i--) {
// Start at the back and move it
// to the position of the segment in front of it
snakeXs[i] = snakeXs[i - 1];
snakeYs[i] = snakeYs[i - 1];
// Exclude the head because
// the head has nothing in front of it
}
// Move the head in the appropriate heading
switch (heading) {
case UP:
snakeYs[0]--;
break;
case RIGHT:
snakeXs[0]++;
break;
case DOWN:
snakeYs[0]++;
break;
case LEFT:
snakeXs[0]--;
break;
}
}
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
private void moveSnake(){
// Move the body
for (int i = snakeLength; i > 0; i--) {
// Start at the back and move it
// to the position of the segment in front of it
snakeXs[i] = snakeXs[i - 1];
snakeYs[i] = snakeYs[i - 1];
// Exclude the head because
// the head has nothing in front of it
}
// Move the head in the appropriate heading
switch (heading) {
case UP:
snakeYs[0]--;
break;
case RIGHT:
snakeXs[0]++;
break;
case DOWN:
snakeYs[0]++;
break;
case LEFT:
snakeXs[0]--;
break;
}
}
The for loop starts at the last block of the snake in snakeXs and snakeYs and advances it into the location previously occupied by the block ahead of it. When the for loop is complete the last position is in the place the block ahead used to be in and the block that was just behind the head is where the head used to be.
Therefore, as long as we handle the head properly all the other blocks will be correctly positioned too.
To move the head we switch based on the current value of heading and add or subtract 1 from either the heads vertical or horizontal position.
In the detectDeath method, we do collision detection. Notice in the code that follows we check for two things. Has the snake’s head bumped into the edge of the screen and has the snake’s head bumped into a block of the snake’s body?
private boolean detectDeath(){
// Has the snake died?
boolean dead = false;
// Hit the screen edge
if (snakeXs[0] == -1) dead = true;
if (snakeXs[0] >= NUM_BLOCKS_WIDE) dead = true;
if (snakeYs[0] == -1) dead = true;
if (snakeYs[0] == numBlocksHigh) dead = true;
// Eaten itself?
for (int i = snakeLength - 1; i > 0; i--) {
if ((i > 4) && (snakeXs[0] == snakeXs[i]) && (snakeYs[0] == snakeYs[i])) {
dead = true;
}
}
return dead;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private boolean detectDeath(){
// Has the snake died?
boolean dead = false;
// Hit the screen edge
if (snakeXs[0] == -1) dead = true;
if (snakeXs[0] >= NUM_BLOCKS_WIDE) dead = true;
if (snakeYs[0] == -1) dead = true;
if (snakeYs[0] == numBlocksHigh) dead = true;
// Eaten itself?
for (int i = snakeLength - 1; i > 0; i--) {
if ((i > 4) && (snakeXs[0] == snakeXs[i]) && (snakeYs[0] == snakeYs[i])) {
dead = true;
}
}
return dead;
}
If either of the collision possibilities happens then detectDeath returns true to the update method which takes further action.
Coding the update method
This method does three things:
It checks if the head has touched/eaten a mouse. If it has then the eatBob method handles things.
It calls the moveSnake method which was coded previously.
It calls the detectDeath method and if it returns true a sound is played and the game begins again.
All this happens ten times per second because of the way updateRequired will work. We will code updateRequired in a minute. Add the code for the update method.
public void update() {
// Did the head of the snake eat Bob?
if (snakeXs[0] == bobX && snakeYs[0] == bobY) {
eatBob();
}
moveSnake();
if (detectDeath()) {
//start again
soundPool.play(snake_crash, 1, 1, 0, 0, 1);
newGame();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void update() {
// Did the head of the snake eat Bob?
if (snakeXs[0] == bobX && snakeYs[0] == bobY) {
eatBob();
}
moveSnake();
if (detectDeath()) {
//start again
soundPool.play(snake_crash, 1, 1, 0, 0, 1);
newGame();
}
}
Drawing the game
Add all the code for the draw method and then we will go through it.
public void draw() {
// Get a lock on the canvas
if (surfaceHolder.getSurface().isValid()) {
canvas = surfaceHolder.lockCanvas();
// Fill the screen with Game Code School blue
canvas.drawColor(Color.argb(255, 26, 128, 182));
// Set the color of the paint to draw the snake white
paint.setColor(Color.argb(255, 255, 255, 255));
// Scale the HUD text
paint.setTextSize(90);
canvas.drawText("Score:" + score, 10, 70, paint);
// Draw the snake one block at a time
for (int i = 0; i < snakeLength; i++) {
canvas.drawRect(snakeXs[i] * blockSize,
(snakeYs[i] * blockSize),
(snakeXs[i] * blockSize) + blockSize,
(snakeYs[i] * blockSize) + blockSize,
paint);
}
// Set the color of the paint to draw Bob red
paint.setColor(Color.argb(255, 255, 0, 0));
// Draw Bob
canvas.drawRect(bobX * blockSize,
(bobY * blockSize),
(bobX * blockSize) + blockSize,
(bobY * blockSize) + blockSize,
paint);
// Unlock the canvas and reveal the graphics for this frame
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
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
public void draw() {
// Get a lock on the canvas
if (surfaceHolder.getSurface().isValid()) {
canvas = surfaceHolder.lockCanvas();
// Fill the screen with Game Code School blue
canvas.drawColor(Color.argb(255, 26, 128, 182));
// Set the color of the paint to draw the snake white
paint.setColor(Color.argb(255, 255, 255, 255));
// Scale the HUD text
paint.setTextSize(90);
canvas.drawText("Score:" + score, 10, 70, paint);
// Draw the snake one block at a time
for (int i = 0; i < snakeLength; i++) {
canvas.drawRect(snakeXs[i] * blockSize,
(snakeYs[i] * blockSize),
(snakeXs[i] * blockSize) + blockSize,
(snakeYs[i] * blockSize) + blockSize,
paint);
}
// Set the color of the paint to draw Bob red
paint.setColor(Color.argb(255, 255, 0, 0));
// Draw Bob
canvas.drawRect(bobX * blockSize,
(bobY * blockSize),
(bobX * blockSize) + blockSize,
(bobY * blockSize) + blockSize,
paint);
// Unlock the canvas and reveal the graphics for this frame
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
First, we lock the surface which is required by Android. If this works, we clear the screen with drawColor and then change the color of all future objects we will draw by calling setColor. We do this once for the snake and once for Bob. Now we draw the text for the score.
We use a for loop to draw a block/square to represent each block of the snake. The code positions the blocks to screen coordinates by using their grid positions(contained in the array) multiplied by blockSize which was determined in the constructor based on screen resolution.
Now we can draw single block to represent Bob.
Coding updateRequired
We are almost done!
The updateRequired method will let us know if the nextFrameTime variable has been exceeded by the actual current time. If it has then a new time is retrieved and put back in nextFrameTime. The method then returns true allowing draw and update to execute. If not, false is returned and the next frame is delayed until it is time.
You can now add the updateRequired method.
public boolean updateRequired() {
// Are we due to update the frame
if(nextFrameTime <= System.currentTimeMillis()){
// Tenth of a second has passed
// Setup when the next update will be triggered
nextFrameTime =System.currentTimeMillis() + MILLIS_PER_SECOND / FPS;
// Return true so that the update and draw
// functions are executed
return true;
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean updateRequired() {
// Are we due to update the frame
if(nextFrameTime <= System.currentTimeMillis()){
// Tenth of a second has passed
// Setup when the next update will be triggered
nextFrameTime =System.currentTimeMillis() + MILLIS_PER_SECOND / FPS;
// Return true so that the update and draw
// functions are executed
return true;
}
return false;
}
Handling screen touches (player input)
The final code handles the player removing their finger. Holding won’t work. The onTouchEvent method uses motionEvent.getAction to detect MotionEvent.ACTION_UP. This notifies us the player’s finger has left the screen. We then use motionEvent.getX() to determine if that action was on the left or the right of the screen.
If they tap on the left side of the screen then the snake moves to the next direction in the enumeration going anti-clockwise if they tap on the right then it’s clockwise.
It’s meant to be awkward, it is authentic to the original. Add this code to handle touches on the screen.
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
if (motionEvent.getX() >= screenX / 2) {
switch(heading){
case UP:
heading = Heading.RIGHT;
break;
case RIGHT:
heading = Heading.DOWN;
break;
case DOWN:
heading = Heading.LEFT;
break;
case LEFT:
heading = Heading.UP;
break;
}
} else {
switch(heading){
case UP:
heading = Heading.LEFT;
break;
case LEFT:
heading = Heading.DOWN;
break;
case DOWN:
heading = Heading.RIGHT;
break;
case RIGHT:
heading = Heading.UP;
break;
}
}
}
return true;
}
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
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
if (motionEvent.getX() >= screenX / 2) {
switch(heading){
case UP:
heading = Heading.RIGHT;
break;
case RIGHT:
heading = Heading.DOWN;
break;
case DOWN:
heading = Heading.LEFT;
break;
case LEFT:
heading = Heading.UP;
break;
}
} else {
switch(heading){
case UP:
heading = Heading.LEFT;
break;
case LEFT:
heading = Heading.DOWN;
break;
case DOWN:
heading = Heading.RIGHT;
break;
case RIGHT:
heading = Heading.UP;
break;
}
}
}
return true;
}