As the title suggests, in this project we will build a simple snake-style game. This style of game has been around since the mid-1970s under other names like Worm and Surround. I played this thing for many hours on the ancient Sword M5 and the “green screen” Sharp MZ80 back in the 1980s. Snake finally reached global acclaim in the 2000s however, when it was supplied as standard with a whole generation of Nokia mobile phones.
Take a look at this image to see the game at the start.
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 that will control a thread in a class that 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.
1 2 3 4 5 6 7 8 9 10 11 12 |
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.
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 |
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 the 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 into the SnakeEngine constructor. The last thing we do is use snakeEngine to be the View of the SnakeActivity.
[widgets_on_pages id=”udemy_advert_java_2″]
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 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.
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.
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 override 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.
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.
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.
What follows is we initialize surfaceHolder and paint.
[widgets_on_pages id=”udemy_advert_java_3″]
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 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.
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 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 ensuring 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.
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.
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.
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.
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?
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.
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.
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 a 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.
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.
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; } |
You can now play the game!
Thank you so much that was really helpful. I wonder how can I show alert dialogue or something when player loses.
Thanks for the message. The easiest way is to just create a graphic to represent a dialog, load it in a bitmap and show it at the apptopriate moment. Most of the other games on this site show how to load and draw bitmaps.
And if I want to open another activity or show a toast, etc ? I have tried runOnUiThread and other things but it didn’t work for me ! Thanks in advance.
You can do these things by using an Intent and Toast respectively by using a reference to the main Activity. It is better, however to build the different “screens” of your game by abstracting the conceptyourself rather than switching Activity. The simplest example of how this might work would be to have a drawHomeScreen function and a updateHomeScreen function that are called depending upon whether the player is currently playing or on a different screen. You coucld then provide the player with somewhere to tap to trigger which methods get called.
Hi, i followed this tutorial to the letter, but it won’t run.
I get “Android resource linking failed” error and the following:
error: resource android:style/TextAppearance.Material not found.
error: resource android:style/TextAppearance.Material.Body1 not found.
error: resource android:style/TextAppearance.Material.Body2 not found.
error: resource android:style/TextAppearance.Material.Button not found.
error: resource android:style/TextAppearance.Material.Caption not found.
error: resource android:style/TextAppearance.Material.Display1 not found.
error: resource android:style/TextAppearance.Material.Display2 not found.
error: resource android:style/TextAppearance.Material.Display3 not found.
error: resource android:style/TextAppearance.Material.Display4 not found.
error: resource android:style/TextAppearance.Material.Headline not found.
error: resource android:style/TextAppearance.Material.Inverse not found.
error: resource android:style/TextAppearance.Material.Large not found.
error: resource android:style/TextAppearance.Material.Large.Inverse not found.
error: resource android:style/TextAppearance.Material.Widget.PopupMenu.Large not found.
error: resource android:style/TextAppearance.Material.Widget.PopupMenu.Small not found.
error: resource android:style/TextAppearance.Material.Medium not found.
error: resource android:style/TextAppearance.Material.Medium.Inverse not found.
error: resource android:style/TextAppearance.Material.Menu not found.
error: resource android:style/TextAppearance.Material.SearchResult.Subtitle not found.
error: resource android:style/TextAppearance.Material.SearchResult.Title not found.
Any help would be appreciated
Hi there,
One possibility is that you have used appcompatactivity instead of plain activity to make the activity class. Double check that and if not I will look into it more deeply.
I double checked, used plain activity.
My best guess then then is that there is an error in the manifest file.
It might be because Android Studio 3.2 and later doesn’t allow naming the Activity during project creation and defaults to MainActivity. Change SnakeActivity to MainActivity or right click on MainActivity in the project explorer and refactor to SnakeActivity. Also make sure that the changes in the manifest file reflect either Snake… or Main… whichever you choose.
If this is the solution I probably need to update every single Android tutorial.:-(
I used refactor to rename to SnakeActivity and i just checked now to see if it all matches is the manifest file and it does. The only thing i can think of at this point is:
Did i introduce these lines properly?
At some point i also figured it might have something to do with the .ogg files (took 2 sounds from a different project on the site, as you suggested), but i already checked – the file names match the ones in the code.
PS: Really grateful for your prompt replies and help. Thanks
This code didn’t get in the previous reply
activity android:name=”.SnakeActivity”
android:theme=”@android:style/Theme.NoTitleBar.Fullscreen”
android:screenOrientation=”landscape”
There is lots going on behind the scenes. It might be easier to start again. I just started a new project on AS 3.3.2 using Empty Activity template. I left the class name as MainActivity but changed AppCompatActivity to Activity. I pasted the rest of the code in around it.
import android.app.Activity;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
public class MainActivity extends Activity {
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);
}
And it worked.
Also, I have just noticed a typo in the part of the tutorial that explains how to make the game full screen. It says:
…immediately below ParallaxActivity but BEFORE the newly positioned >
It should say:
…immediately below SnakeActivity but BEFORE the newly positioned >
Although, for you, while following my preceding steps, it will should be:
…immediately below MainActivity but BEFORE the newly positioned >
I think the site needs a bit of an overhaul. I am tied up with books for another couple of months and then I will add lots more tutorials including some videos and bring tese existing tutorials up to date.
I hope this works for you.
John
Well definitely on the right track, no more errors. The only problem is that it just shows a black screen.
The first thing that comes to mind is that onResume and onPause are missing? I showed some code for MainActivity in my previous post but only showed onCreate thinking you would add onResume and onPause. If onResume isn’t there the thread will never start, no drawing occurs and the output will be blank.
We must be close.
Yes,when i wrote the second project i completely forgot about the onResume and onPause. It works great now, thank you again
In my project there are no errors, but when I run the program on my emulator the app stops
There will be the details of the error in red in the logcat window at the bottom of Android Studio; probably in the form of a crash report. If you give me the highlights of the error text along with any other information you might think is relevant and I will try and work out the cause.
2019-05-01 09:03:02.218 7140-7140/com.example.snake E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.snake, PID: 7140
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.snake/com.example.snake.SnakeActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
at android.support.v7.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:555)
at android.support.v7.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:518)
at android.support.v7.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:466)
at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:140)
at com.example.snake.SnakeActivity.onCreate(SnakeActivity.java:20)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
this is the only error that shows up now, i had the sound messed up but i think I fixed that, so now its just this as far a errors go in the log cat
The line, java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity, is an error you might get if you extend AppCompatActivity instead of Activity? Does that help?
yes this does help a lot thank you so much
I have some problems with onResume and onPause:
@Override
protected void onResume() {
super.onResume();
snakeEngine.resume()RED!;
}
// Stop the thread in snakeEngine
@Override
protected void onPause() {
super.onPause();
snakeEngine.pause()RED!;
}
.resume and .pause aren’t initialized. Help, please:)
Hi Yuri, are you saying that those to lines appear red? If so, have you coded the SnakeEngine class yet because they will be errors until you code them. If you are showing me your code as it is then the RED! should be removed.
I’ve solved that problem. How the app doesn’t start. Logcat:
java.lang.NullPointerException: Attempt to read from null array
at com.redhead.y14.snake.SnakeEngine.update(SnakeEngine.java:257)
at com.redhead.y14.snake.SnakeEngine.run(SnakeEngine.java:149)
at java.lang.Thread.run(Thread.java:776)
Could I contact with you on email? I will send you the whole my code
I could have a guess that these lines might be missing
// If you score 200 you are rewarded with a crash achievement!
snakeXs = new int[200];
snakeYs = new int[200];
Getting class errors with the end of the snake engine class, expecting a class or interface after closing off the snake engine class. any reason for this?
Sounds like an extra curly bracket ( }) might be at fault.
I’m completely new to programming. Any tips to start with?
It all comes down to your goals as there is no best way; although some ways are harder/easier etc. Try this article to refine where you might like to start. http://gamecodeschool.com/blog/making-games-where-do-i-start/
I have error: non-static method getX() cannot be referenced from a static context
How can I solve it?
A possible cause for this would be if you typed the class name instead of the instance name. I.e.
if (MotionEvent.getX() >= screenX / 2) {
instead of:
if (motionEvent.getX() >= screenX / 2) {
I hope this helps.
Thank you. It helped.
But I have second problem. If I run applications, I have a whole black screen , no blue and without snake or Bob. It looks like the graphics did not work. I send code of draw() below:
public void draw(){
if (surfaceHolder.getSurface().isValid()){
canvas=surfaceHolder.lockCanvas();
canvas.drawColor(Color.argb(255,26,128,182));
paint.setColor(Color.argb(255,255,255,255));
paint.setTextSize(90);
canvas.drawText(“Score: “+score, 10, 70, paint);
for (int i=0;i<snakeLength;i++){
canvas.drawRect(snakeXs[i]*blockSize,
snakeYs[i]*blockSize,
(snakeXs[i]*blockSize)+blockSize,
(snakeYs[i]*blockSize)+blockSize,
paint);
}
paint.setColor(Color.argb(255,255,0,0));
canvas.drawRect(bobX*blockSize,
bobY*blockSize,
(bobX*blockSize)+blockSize,
(bobY*blockSize)+blockSize,
paint);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
I can't find a mistake. Maybe it depends on the version on which the application is made?
A black screen with no errors? This could be that you haven’t coded onResume and onPause in the Activity class? And therefor the thread isn’t started. Code looks OK. Let me know if this fixes it.
I find my mistake . Now all is ok, Thank you very much for your help
I have a problem with this two lines
snakeEngine = new SanekEngine(this, size);
|__________|
this part tolds me that it cannot find the symbol
and in setContentView(snakeEngine);
|_________|
this part says that cannot find the method ´setContentView
What it could be my problem?
Hi there, check these things.
Have you created SnakeEngine and extended SurfaceView with it yet?
In your comment SnakeEngine is misspelled. Is it spelt correctly in your code?
Is your Activity class file name exactly the same as the name declared in the code. When this tutorial was created Android Studio asked you to name the Activity. Latest version uses default MainActivity. If not, adjust the code to match MainActivity.
Hope this helps.
So, i dont have any error, but when i run the app, all the screen is black. I checked the collors in draw method, and its all the same, any idea what it could be my problem?
Hmm.
Check this line:
// Make snakeEngine the view of the Activity
setContentView(snakeEngine);
And this line:
// Unlock the canvas and reveal the graphics for this frame
surfaceHolder.unlockCanvasAndPost(canvas);
Any errors in the Run window?
Thank you so much it helped me. It works correctly, but when detectDeath i want to add a Intent than runs another activity that asks the player if wants to try again or end the game… so how can i do it? thanks in advance:)
You can switch activities with code like this:
Intent myIntent = new Intent(this, MyActivityName.class);
startActivity(myIntent);
It is better, however to build the different “screens” of your game by abstracting the concept yourself rather than switching Activity. The simplest example of how this might work would be to have a drawHomeScreen function and a updateHomeScreen function that are called depending upon whether the player is currently playing or on a different screen. You could then provide the player with somewhere to tap to trigger which methods get called.
i cant use intent in class snakeEngine i dont know why.
anyway i dont know how it works drawHomeScreen and updateHomeScreen function, i have searched for information and nothing. Thanks in advanced.
Intent is part of Activity so you need to use context which is initialized in the SnakeEngine constructor. However, almost certainly the best way is to use SnakeEngine to implement the other parts of your game. The method names are just suggestions for names you might like to use for methods you might to implement these other parts of your game. At the moment you update everything in the update method and draw everything in the draw method. Therefore, if you were implementing a home screen for your game you could create different methods that did the things you want while the player is on the home screen. For example, if your home screen says “Welcome to Snake” the drawHomeScreen method would just be a simplified version of the current draw method which just draws the aforementioned text. You would need some boolean variable(s) to track which screen the player is viewing (home or game) and call the appropriate draw method. The boolean variables would also determine whether you called update or updateHomeScreen. The updateHomeScreen method might do nothing or perhaps it would have an animation on it, in which case you would write code to update the animation. In addition you will also need to have different input handling in in onTouchEvent. The same booleans would determine whether you used the current snake handling code or perhaps a touch anywhere on the screen to start the game. As you get more sophisticated you could add buttons for different options, perhaps a high score screen and a settings screen. Each screen would be implemented using its own unique draw…, update.. method as well as its own input handling.
This isn’t something I can quickly flesh out in full for you. I guess it would need another tutorial but I hope this info gets you started.
ok, thanks, i just implemented everything now, and the game works perfectly.
I have just a black screen, why?
Check the call to setContentView in the Activity class is using the surface view instance.
Thanks 4 the tutorial but i got a “little” problem with the sound effects. No error and the game is running, but no sound effects played when the snake eat bobs or hitting wall. Any idea to fix this?
The way the sound files are added is vital. If you drag and drop them into Android Studio they won’t work. 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.
Also, check the Run window for output like “Failed to load sound file…” etc.
The SoundPool constructor I gave used is deprecated. I did it to save a few long lines of code but it should still work. As a last resort have a look at this more modern code that checks for the version of Android to choose the best way to setup a sound pool. Note this code is a guide to be amended not a copy & paste replacement.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
mSP = new SoundPool.Builder()
.setMaxStreams(5)
.setAudioAttributes(audioAttributes)
.build();
} else {
mSP = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
}
// Open each of the sound files in turn
// and load them in to Ram ready to play
// The try-catch blocks handle when this fails
// and is required.
try{
AssetManager assetManager = context.getAssets();
AssetFileDescriptor descriptor;
descriptor = assetManager.openFd(“beep.ogg”);
mBeepID = mSP.load(descriptor, 0);
descriptor = assetManager.openFd(“boop.ogg”);
mBoopID = mSP.load(descriptor, 0);
descriptor = assetManager.openFd(“bop.ogg”);
mBopID = mSP.load(descriptor, 0);
descriptor = assetManager.openFd(“miss.ogg”);
mMissID = mSP.load(descriptor, 0);
}catch(IOException e){
Log.e(“error”, “failed to load sound files”);
}
}
Hi, is it possible to rework this code using accelerometer sensor for moving the snake?
Yes but not something I can explain in a comment.
Hi!
the application works partially. I’m running on my personal phone and when i click on the screen the application closes.
What to do?
Run in debugging mode attach usb to pc and android studio will give the error. 99% will be null pointer exception from uninitialized reference
Hi there can you help me with an error…
It shows an error
class SnakeEngine extends SurfaceView implements Runnable {
^^^^^^^^^^^
The type SnakeEngine must implement the inherited abstract method Runnable.run()
———-
1 problem (1 error)
Hi
I wanted the app to have a welcome screen that started the main activity on the click of a button. I set the welcome activity as the launcher activity and added code for the button and for passing an intent to the main activity. But the app keeps stopping. Is there a way to correct this?