How to Make an Android App (Snake)

3,927

35

9

Computer Science student in the UK

Intro: How to Make an Android App (Snake)

Learning to code is an extraordinarily worthwhile skill, and it's incredibly rewarding and satisfying to see your code come to life in the form of some tangible product, whether it be some Arduino contraption, a website you built, or in this case, a game you can play and share with others.

Above is a time-lapse of coding the game, the whole thing took around 40 minutes.

This tutorial will use Java to show you how to use the LibGDX framework to get a working game on your android phone and your computer. The great thing about LibGDX is that you code your game and can then export to a variety of platforms, namely PC (Mac, Linux, and Windows), Android, IOS, and HTML5. This tutorial will focus on Android but if you want to deploy to iOS you can code the game following the tutorial, then follow these instructions to deploy to iOS.

I will show you how to get the basic game working on your phone with touch controls. After making this Instructable I also added more features like gesture controls and released it to the Google Play Store, which you can see here.

This is by no means intended to be a straight-up Java tutorial and having some experience with Java will help you greatly here, there are already plenty of excellent Java tutorials online, so do one of those before following this. I do believe that 'doing' is one of the best ways of learning, and putting your Java skills in the context of a real thing can really help you learn, understand, and remember key concepts.

As always, comment questions or message me and I'll do my best to help.

Oh and feel free to offer improvements or tips for my code, I'm by no means an expert and am always willing to learn. And do let me know if I've made any mistakes in the included text files/screenshots/instructions.

Let's jump right in.

Step 1: Your Programming Environment and You

Install an IDE

It is definitely worthwhile using an IDE (Integrated Development Environment) to simplify and speed up things. I use IntelliJ IDEA and so the screenshots and IDE-specific instructions will relate to that. Eclipse is a classic IDE you could use instead. If you're new to coding it may be easier to use IntelliJ IDEA to make it easier to follow along, as my instructions are IDEA-specific.

Install the Android SDK

To export your project to Android you'll need the Android SDK which can be found here. Android Studio isn't needed for this tutorial and you can download the SDK separately (scroll down to the bottom), but installing Android Studio will install the SDK for you.

Once you have an IDE and Android Studio/SDK, proceed to the next step to install LibGDX.

Step 2: LibGDX

LibGDX is a cross-platform, open source, fairly well-documented framework for making games. Essentially, LibGDX will handle all the boring boilerplate stuff and free you up to make a game quickly. Without LibGDX you'd have to worry about how to draw an image on a screen at a particular location, along with a plethora of other boring stuff. Not only is LibGDX easy to use, it's easy to set up and install.

Follow this link to LibGDX's download page and download the 'setup app'. Create yourself a folder for all of the Android games you're going to make after this one, and within that create a folder for this project specifically called 'snake'. Put the gdx-setup.jar you downloaded in the outer folder so that it's easy to find for future projects. This jar file will download all the files you need and create a base project, upon which you'll build your game. Go ahead and open it up and you'll see the Project Setup screen.

Setup

  • Name: You can choose your own name for this, I just called mine 'Snake'
  • Package: A Java package is a way of organising classes, similar to a folder on a computer. By convention, Java programmers name their packages after some internet domain that they own (reversed), for example, com.myWebsite.someProject. Java packages need to be unique to avoid clashes with other Java programmers' work, and since domains are unique they are used to name packages. If you have a website use that, but if you don't, don't worry too much. For a small project, as long as you pick a package name that is probably going to be unique then you'll be fine. Try not to pick one that starts with 'com.' if you don't actually own that website. A package that could be fine would be 'personal.yourNameHere.projectname'. As soon as your indie games studio gets big though, you'll want to start using proper package names.
  • Game Class: This is just the name of the main game class. I called mine 'Main', but you can call it what you like.
  • Destination: This is where the base game files will be downloaded. Pick the folder that you made for the game.
  • Android SDK: The location of the SDK you installed from the previous step. If you installed Android studio, you can find the location by launching Android Studio and going to Configure -> SDK Manager
  • Sub Projects: These are the platforms you would like your game to run on. I just chose Desktop and Android, but feel free to tick more if you want to deploy to other platforms.
  • Extensions: These are some very useful libraries that will definitely come in useful in your Indie Gaming career. For now, all we need is 'Tools', so check that and leave the others unchecked.

Before generating the project, click Advanced-> and make sure 'IDEA' is checked to automatically generate project files for IntelliJ IDEA (check 'Eclipse' if you are using that). Press Save, then Generate.

(Side note: You can get pretty cheap domains online if you want your own guaranteed package name)

Step 3: Into the IDE

So you have your IDE and SDK installed, the LibGDX files are downloaded and in the right place, it's time to get coding. There are a couple of things to sort out first. Launch your IDE choose 'Open' or 'Open Project'. If you ticked the 'IDEA' or 'Eclipse' option in the previous step, your project files will have been generated. For IDEA, navigate to your game directory and double-click the 'yourGame.ipr' file.

  • In the bottom right a box will pop up prompting you 'Import Gradle Project'. Press this, then in the pop up make sure to untick 'Create separate module per source set'. Leave everything else as it is and press ok. You'll see a loading bar at the bottom and give this some time to complete.
  • Once this is done, close and re-open the project and you'll get a prompt to update Gradle to 3.3 (at the time of writing). Do this, and you'll see that an 'Android' configuration has been made for you (top of the screen to the left of the green 'Run' arrow).
  • Double click the folder icon with the name of your game in the top left to reveal the file browser.

You'll notice a number of folders. The folders for 'android' and 'desktop' (and 'ios' etc if you kept those) contain files to launch the game on a specific platform. For now, we're much more interested in the 'core' folder.

  • Double-click folders to open them. Open core->src and you'll see a 'Class' called 'Main' (or whatever you chose to name your Main class). The 'C' icon next to it tells you that it's a class file. This has been automatically generated for you by the LibGDX setup app and has come preloaded with a simple application.
  • Double click the Main class in the Core folder and you'll see its code.

I'll explain what this code is doing in the next step, but for now, let's run the simple auto-generated application to see what it's doing. We need to add a 'configuration' for the Desktop Launcher so that we can test out our game on the computer. This will tell the IDE that we need to use the files in the 'Desktop' folder to launch the game when we press the green arrow.

  • Click the drop-down box which currently says 'Android', then click 'Edit Configurations'.
  • In the Configurations window that has appeared, click the 'plus' in the top left to add a configuration. Select 'Application'.
  • Now we tell it that we want to use the Desktop files. In 'Main class' type "your.package.here.desktop.DesktopLauncher". If you open up the 'desktop' folder in the explorer on the left of the IDE you'll see that there's a 'DesktopLauncher' class in the src folder.
  • In the 'Use classpath of module' box select 'Desktop'.
  • Add a suitable name in the box right up the to, like 'Desktop'.
  • Change the working directory to yourGameFolder/android/assets - this folder is where textures and sounds should go
  • Click OK

Make sure that 'Desktop' is the currently selected Configuration using the box that used to say Android, then click 'Run'. If all went well, you should see a window appear with a horrible red background and a BadLogic logo. You're ready to start coding!

If you don't see this window, double check that you've followed the steps carefully, and feel free to ask for help by posting your error message below.

Step 4: The Game Loop

Code for this step is provided in the text file and screenshots. Remember to change the package name in the text file if you use it.

LibGDX handles all the hard stuff for you. You just have to write the code that gets executed every 'tick'. You have underlying logic for the game that holds stuff like the position of entities, their health, their name, anything you could think of that needs to be stored and updated. Then to turn that in a game all you need is a bunch of images flying around the screen that represent the logic underneath.

The game loop:

LibGDX calls your 'render' method that you can see in the Main class. Your code in the render method takes some time to execute, images are drawn onto the screen, and when it's done, the engine calls render again. You can make something move by changing its position every time the render method is called. Let's do this now.

Add a member variable x to the Main class:

int x = 0;

In render, increment x:

x++;

Change the image's x position to use your x position:

batch.draw(img, x, 0);

Now every time render is called the image will move slightly to the right. This is because the x position of the image is increased every time the image is drawn.

As for the other provided methods, create() is called when the game is started. This is where the images are loaded and the SpriteBatch is created. Dispose() gets rid of unused resources. It's important to make sure to dispose of unused resources like images, fonts, and sounds when you are done with them. More details on that here if you're interested, but for this project, we won't have any assets to dispose of.

The SpriteBatch basically sends many images to the GPU at once to improve efficiency. More details here if you're interested, but don't worry we won't need to use the SpriteBatch for this game.

Step 5: Screens

We'll be using 'Screens' to implement the game. This is an interface provided for you by LibGDX, and like the Main class, each screen has a create, render, and dispose method, along with some others that you don't have to worry about yet.

You can think of screens as literal screens of a game. You might have one screen for level 1, another for level 2, another for the main menu, another for the options menu etc. For now, let's make a screen for the snake game, called 'GameScreen'.

In the 'core->src->your.package' folder, right click on the package and click New->Class, or right-click on your Main class and click New-> Class. Name this one GameScreen (remember in Java it's conventional to capitalise the first letter of classes). Make this class implement the Screen interface by typing 'implements Screen' after the class name, importing the Screen class from com.badlogic.gdx.screen.

public class GameScreen implements Screen

You should see a red line appear underneath it, saying that you need to implement methods in 'Screen'. Screen is an interface with abstract methods - methods that have no code in them - that you need to implement. Press Ctrl + i, then Enter to automatically add these methods to your GameScreen class. you should see 7 methods appear. The main one we'll add to is the 'render' method, which is called every game tick. It's just like the render method in your Main class, but it has a 'delta' float supplied to it.

Back to Main

In order to use Screens, we need to change something in the Main class. Main currently extends ApplicationAdapter, but we need it to extend Game. All this does is allow us to use Screens, as Game itself extends ApplicationAdapter. Change ApplicationAdapter to Game.

public class Main extends Game

We next need to add super.render() in the Main render() method, which needs to be done now we're extending Game, not ApplicationAdapter. Make it the first thing in the render() method.

Next, we don't actually need to draw anything in the Main class, since this will all happen in the GameScreen class. Because of this, delete everything in the Main render() method except super.render():

public void render () {
	super.render();
}

Since we're not drawing anything, we don't need the default example image provided in the class. Remove the 'Texture img' state, and any other references to the image.

Finally, we need a way of telling Main which Screen we want to switch to when the game starts. This would normally be a loading screen or the Main Menu, but for now, let's switch to our GameScreen Screen.

Add this at the very end of the create() method.

this.setScreen(new GameScreen());

You can now remove any import statements that are not used (greyed out).

Step 6: Delta

The delta value supplied to your render method by the game engine is the time taken for the last frame to draw, or the time in between the last frame being drawn and this frame to start drawing. It is measured in seconds, and typical values of delta may be around 0.008s.

This is actually an incredibly useful, simple, smart way to make things move at the speed you want them to.

The Problem

Let's imagine that in your render method you tell an image to move one unit to the right. The computer processes your code, calls the render method, in a loop over and over again. If you put this code on a slow computer, it will take longer to process the code, and your image will effectively move slower. If you put it on a fast computer, the image will move faster. What if the image represents your character in the game, shouldn't the character move at the same speed, regardless of the phone the game is running on?

The Solution

Introduce the hero delta, here to save your game from the woes of different computers/phones. Since delta is the time in between this frame and the last frame, you can scale the player's speed by delta to ensure that the player moves at the same speed.

Suppose you want your character to move 5 in-game units in 1 second. In your code, you give the player some state 'int velocity = 5;'.

Distance = speed x time, and we have the speed, and we have the time (delta). In the render method you calculate the distance the player should move:

float distanceToMove = velocity*delta; //example code, you don't need to add this

And you add this to the player's x:

x += distanceToMove; //also example code

Now, if delta increases, distanceToMove increases. This is what should happen, as if the time between frames is slower the player should move more distance between each frame. By multiplying velocity by delta you ensure that player velocity is constant no matter what platform the game is running on.

Confession: We're not actually going to use this technique in the snake game. Instead we're using a grid-based system and using delta as a timer to know when to update the game logic because it's a better fit for snake, but still I think it's really important for you to know this because you'll inevitably go on to build some platformer or top-down game or something and that's where this technique is invaluable.

Ok, let's whack out some quick code to show you delta values. In your render method in the GameScreen class simply put:

System.out.println(delta);
>>Epilepsy Warning<< - If you run the program now you'll see some flashing images because we haven't cleared the screen in the render method yet

Run the program and see the delta values stream onto the screen. Every new delta value is a new game tick. Start to feel the unlimited potential and opportunity to unleash your indie game creativity. You'll notice that the game just shows some weird flashing image. This is because we haven't cleared the screen. Add this to the beginning of the render method to clear the screen:

Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

Now you should just see a black screen when you run it. If you change the first 3 numbers from 0,0,0 to something else (they should be between 0 and 1) you can change the colour of the background. These are RGB values (the last value is alpha, for transparency), use an online colour picker tool to select some colours to try.

Step 7: Relax

Remember to frequently focus on something far away, stand up, stretch your arms and legs, and take a short break. You'll stay healthier and more focused for longer if you take a breather every now and then.

Aight that's enough, back to it.

Step 8: Camera

Last thing to do before we start actually coding the game I promise. We need to set up a camera and viewport so LibGDX knows we're making a 2D game and it knows how to project images up onto the screen. Don't worry if you don't understand this step, I'm not sure I properly understand it myself. Just add this code in the right places and everything will be fine.

In the GameScreen class, add some state for the width and height of the game screen. Since we're making a snake game, a portrait orientation ought to work. Remember state needs to be added inside the class declaration, and above any methods.

private int width = 600;
private int height = 1000;

Next add the camera, importing Camera from badLogic:

private OrthographicCamera camera = new OrthographicCamera(width, height);

Add the viewport state - we haven't initialised it yet, also importing from badLogic:

private Viewport viewport;

We need to do something to the camera, then initialise the viewport using that edited camera. For this, we'll need a constructor, which is called whenever you build the class. It's a method like any other but it has no return type, and the name of the method is the name of the class.

public GameScreen() {
	camera.setToOrtho(false, width, height);
	viewport = new FitViewport(width, height, camera);
	viewport.apply();
}

The 'false' in setToOrtho means that y increases as objects move up the screen. Some prefer it the opposite way around. I don't.

A FitViewport ensures that everything is shown on screen. Not all phones will have a 10:6 aspect ratio (our 600 width and 1000 height). If a screen doesn't fit the aspect ratio, the FitViewport will scale the game down until it is all shown on screen, leaving black bars on the top and bottom. A FillViewport stretches to fill the screen, and might be useful as a HUD, but we don't need that for now.

In the render method add this at the beginning:

camera.update();
viewport.apply();

In the resize(int width, int height) method add:

viewport.update(width, height);

This updates the viewport when the game window is resized (for example if you drag the edges to make it bigger on a computer).

This is the basic camera-viewport setup you'll want for any game you make (with a different width and height if you want). Further reading on this can be found here.

Main game reference:

We want to be able to access the batch in our Main class from within the GameScreen class. We can do this by passing a reference to it in the constructor. Change the constructor to take a 'Main game' argument, and add private Main game state.

Add this state at the top:

private Main game;

Initialise it in the constructor:

public gameScreen(Main game) {
	this.game = game;
	//etc....
}

Finally go back to the main class and change it to pass a reference of itself to the new GameScreen:

this.setScreen(new GameScreen(this));

Step 9: Finally Actually Coding the Thing!

Time to put a snake on the screen. Let's create a new class called GameState that will hold our underlying logic for the snake game. Add two empty methods, one called update(float delta), and one called draw(). We'll create an instance of this in the GameScreen, and call the update and draw methods from within GameScreen.render.

Back in GameScreen add:

private GameState gameState = new GameState();

as state.

In GameScreen's render method add

gameState.update(delta); 

near the top. This will call gameState's update method every tick and give it delta.

Add

gameState.draw();

after the screen clear, and remove the System.out.println while you're at it, we don't need it anymore.

The Board Outline

Let's create a square board for the game to take place on. A 30x30 board is a good choice. Add state to GameState for the board size:

private int boardSize = 30;

We'll need a ShapeRenderer to draw rectangles for us. For now, this is simpler than creating textures and loading them in. Instead, you specify the coordinates of a rectangle you want to be filled. Add this state to GameState:

private ShapeRenderer shapeRenderer = new ShapeRenderer();

We need to specify how far up our board is on the screen. To fill the screen, the board should be 600 units wide, and for a square board 600 units tall. Since we have 1000 units of height, let's put the board 400 units up to give as much space for the touch controls underneath it as possible. To make this adjustable, it's good to have it as state in GameState that can be changed:

private int yOffset = 400;

Now let's draw the board outline itself. To use shapeRenderer we'll need the reference to GameScreen's camera. We'll also need GameScreen's width and height state so we know how big the board should be. To do this, make the draw function take some more arguments:

public void draw(int width, int height, OrthographicCamera camera) {

importing OrthographicCamera from badlogic. Now we need to change GameScreen to pass it this information. In the render method change gameState.draw() to this:

gameState.draw(width, height, camera);

Back in GameState let's set up the ShapeRenderer in the draw method. Add this code in draw:

shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled)
//rectangle drawing happens here
shapeRenderer.end();

This sets up the ShapeRenderer to draw filled shapes in the correct position. To draw rectangles, we just need to add code in between the shapeRenderer.begin and shapeRenderer.end.

Do this now. Add this code in between the begin and end:

shapeRenderer.setColor(0,1,1,1);
shapeRenderer.rect(300, 500, 100, 100);

To set the colour to yellow and draw a rectangle on the screen. If you run the game now you should see the rectangle appear.

The easiest way to draw the border is to draw two rectangles, one slightly bigger than the other:

Remove the old rectangle and add these:

shapeRenderer.setColor(1,1,1,1);
shapeRenderer.rect(0, yOffset, width, width);
shapeRenderer.setColor(0,0,0,1);
shapeRenderer.rect(0+5, yOffset+5, width-5*2, width-5*2);

You should now see a board outline if the second rectangle colour is identical to the background clear colour you chose in the GameScreen class. I'm just using black for the retro appeal.

Step 10: The Snake Itself

We're going to be using a Queue to represent the Snake. Specifically, a queue of Bodypart objects, each holding an x and a y.

A queue is a data type where you can add things to the end, and take things off the front. It has a FIFO (first in first out) order, so the earlier you add something the earlier it gets taken out. Think of it as a queue in a shop. Customers come off the front of the queue to get served, and new customers get added to the end.

Using a queue as a snake gets rid of annoying issues like making the body follow the snake's path or trying to remember where the snake has been. To advance the snake, we just add a 'Bodypart' to the front and take one off the end. Easy.

Bodyparts

First, create the Bodypart class. Give it two pieces of state for x and y, and a constructor which takes three values, x,y, and boardSize.

private int x;
private int y;
public Bodypart(int x, int y, int boardSize) {}

By pressing Ctrl+N (Cmd N on Mac) you can automatically create a 'getter' for x and y so that we can access the coordinates safely from outside the class.

public int getX() {
	return x; 
}
public int getY() { 
	return y; 
}

This version of Snake is going to wrap around the edges, but it's easy to edit the code to make you die once you reach the edge. When we advance the snake we add a Bodypart in the direction the snake is going. If the Bodypart is outside the boardSize, we need to 'wrap around' using modular arithmetic.

Add this code in the constructor. The constructor here allows us to do input sanitisation, and makes it impossible to have a bodypart outside the board. The if statements are needed for negative x and y values.

this.x = x % boardSize;
if (this.x<0) this.x += boardSize;
this.y = y % boardSize; 
if (this.y<0) this.y += boardSize;

The Queue

Now go back to GameState and add some Queue state.

private Queue<mBody> = new Queue<>();

Make sure to import Queue from badlogic and not java.util.

We want to start the game with 3 Bodyparts so let's create a constructor for GameState and add them.

public GameState() {
	mBody.addLast(new Bodypart(15,15,boardSize)); //head 
	mBody.addLast(new Bodypart(15,14,boardSize)); 
	mBody.addLast(new Bodypart(15,13,boardSize)); //tail
}

Now we need to create a way to draw all Bodyparts in the right place. We use our ShapeRenderer to draw the Snake.

Important: We need to put this code after the code to draw the board so that the snake draws on top of the board.

Remember to set the colour to white (or any visible colour) first:

float scaleSnake = width/boardSize;
for (Bodypart bp : mBody) {
	shapeRenderer.rect(bp.getX()*scaleSnake, bp.getY()*scaleSnake + yOffset, scaleSnake, scaleSnake);

}

The loop iterates through all Bodyparts and draws them, scaled by the scaleSnake factor which converts from the board size to the screen size.

If you run the game now you should see a 3-square-long snake on the board. Resize the window and the board should stay square.

Step 11: Make It Move!

This project's going to come together really fast now. You've done the work, time to reap the reward.

We're going to use the update method in GameState to add to the queue and take away from it. First, we need a way to limit the speed of the snake. If we add one and take one away every tick it's gonna fly across the screen faster than you can imagine. Let's make a simple timer using delta.

Add state to GameState for the timer:

private float mTimer = 0;

Now in the update method, we need to add delta to the timer every tick:

mTimer += delta;

After a preset time period, we want to reset the timer and advance the snake (update the game logic). Using a simple if statement:

if (mTimer > 0.13f) {
	mTimer = 0; 
	advance(); 	
}

And we need to create an advance() method that adds one to the snake and takes one off the end. This can be private since only GameState should call it:

private void advance() {
	mBody.addFirst(new Bodypart(mBody.first().getX(), mBody.first().getY()+1, boardSize));
	mBody.removeLast();
}

Press Play and your snake should be moving! By changing the number in mTimer > 0.13f you can change the speed of the snake.

Next, we need a way to change direction.

Step 12: Controls

We need to create a controls class. This allows us to filter controls, for example not letting users press buttons they shouldn't, and makes it easy to rebind controls. For now, we'll use the keyboard but we'll add some touch buttons later, which will be easier with a separate controls class. Using a controls class we can make sure players don't turn back on themselves, i.e. they can't move down if they're moving up.

The Controls class will need state for the current direction and the next direction. The Snake only updates once every 0.13 seconds, which means that if the user presses two buttons in that time, we want the most recent press to become the next direction. We also need the current direction to know whether or not to allow a user to press a button.

Let's store the direction as an integer: 0,1,2,3 for up,right,down,left respectively (starting at up and going clockwise).

I really should have set some private final static int state here so that instead of, for example setting the direction to 0 for up, you set it to UP where UP = 0 like this:
private final static int UP = 0;
This would make the code much more readable, and less error-prone. I would recommend doing this for all four directions.

Controls

Add this state to Controls:

private int currentDirection; //0,1,2,3 U,R,D,L 
private int nextDirection;

We also need these methods:

public int getDirection() {}
public void update() {}

update() needs to be called every tick to poll the keyboard for keypresses. getDirection needs to be called every time advance() is called (every time the timer hits 0.13) so that the snake knows which direction it should be going in. Let's add the keypresses in the update() method:

In the if statements we also check the currentDirection to see if the desired keypress is allowed. For example, if the snake is moving up, it shouldn't be able to immediately move down or it would eat itself instantly. The only options really are left and right.

if(Gdx.input.isKeyPressed(Input.Keys.UP) && currentDirection != 2) nextDirection = 0;

else if (Gdx.input.isKeyPressed(Input.Keys.RIGHT) && currentDirection != 3) nextDirection = 1;

else if (Gdx.input.isKeyPressed(Input.Keys.DOWN) && currentDirection != 0) nextDirection = 2;

else if (Gdx.input.isKeyPressed(Input.Keys.LEFT) && currentDirection != 1) nextDirection =3;

Next, add this in getDirection:

currentDirection = nextDirection;
return nextDirection;

It returns the direction so that GameState knows where to put the next body part, and sets the currentDirection to the direction it just gave to GameState.

LibGDX also supports event-based input detection rather than poll-based, which you can read about here.

GameState

Go back to the GameState class so that we can make the snake change direction. We need to create an instance of the Controls class first. Add this state:

private Controls controls = new Controls();

We need to update the controls every tick so we add this to the update method:

controls.update();

Next, we'll change our advance() method. Change the code that's already in there to this:

private void advance() {
	int headX = mBody.first().getX(); 
	int headY = mBody.first().getY();
	switch(controls.getDirection()) {
		case 0: //up 
			mBody.addFirst(new Bodypart(headX, headY+1, boardSize));
			break; 
		case 1: //right 
			mBody.addFirst(new Bodypart(headX+1, headY, boardSize));
			break;
		case 2: //down 
			mBody.addFirst(new Bodypart(headX, headY-1, boardSize));
			break;
		case 3: //left 
			mBody.addFirst(new Bodypart(headX-1, headY, boardSize));
			break;
		default://should never happen 
			mBody.addFirst(new Bodypart(headX, headY+1, boardSize));
			break;
	}
	mBody.removeLast();
}								

Switch statements are like a series of if statements. Instead of writing if(1) else if (2) else if(3).. etc. we can just use a switch statement to clean things up a bit.

Run the game and you should be able to move the snake around with the arrow keys. Time to add food.

Step 13: Food

The Food Class

Making food appear is quite simple. Create yourself a 'Food' class.

We need to be able to get the x and y location of the food so we can draw it, and we need to be able to randomise the food's position.

So let's add some state for x and y:

private int x;
private int y;

And we need getters for these, press Ctrl+N (Cmd N):

public int getX() { return x; }
public int getY() {return y; }

To randomise the position of the food, it needs to know the boardSize. Let's use LibGDX's own MathUtils random method, which simplifies generating a random integer between two points. MathUtils.random(int range) provides a random number between 0 and range inclusive: (import MathUtils from badlogic)

public void randomisePos(int boardSize) {
	x = MathUtils.random(boardSize-1);
	y = MathUtils.random(boardSize-1);
}

Finally, we need a constructor to create the food and randomise its position. We need to give the constructor a board size so it knows where it can legally initially place the food:

public Food(int boardSize) {
	randomisePos(boardSize); 
}

And we are done. Let's head back to GameState and create the food object, and find a way to draw it.

GameState

Create and initialise state for the food with the other state (but underneath boardSize):

private Food mFood = new Food(boardSize);

In the draw method add this to draw the food. Make sure it is under the board outline so it get's drawn on top, and make sure the colour is set to white or some other visible colour. We can use the same scaleSnake factor to draw our food so make sure this gets put under the scaleSnake initialisation.

shapeRenderer.rect(mFood.getX() * scaleSnake, mFood.getY()*scaleSnake + yOffset, scaleSnake, scaleSnake);

If you run the game now you should see the food appear in a random place.

Time to add the ability to eat the food.

Eating Food

Every time the snake advances we need to check if its head is in the same location as the food. If it is, we add one to the snake's length and randomise the food position.

We need some state to hold the current length of the snake. Add this as state:

private int snakeLength = 3;

In advance, underneath the switch statement, we'll detect if the newly placed head is touching the food. If it is, we increment the snakeLength and randomise the food position:

if (mBody.first().getX() == mFood.getX() && mBody.first().getY() == mFood.getY()) {
    snakeLength++;
    mFood.randomisePos(boardSize);
}

This is fine but the snake doesn't ever get any longer because we always remove the tail. We need to detect if the current length of the snake is less than the 'snakeLength' variable, and if it is, we do not remove one from the tail. In other words, we only remove the last element if the current size of the snake is equal to 'snakeLength'.

if (mBody.size - 1 == snakeLength) {
    mBody.removeLast();
}

You'll notice that we use 'mBody.size - 1'. This is because this statement comes after the switch statement that adds one to the head, so we need to account for that extra bodypart.

Press play and you should be able to eat food and get longer.

Step 14: Death

It's not much of a game yet. There's no challenge if there's no way to fail, so let's add a fail condition.

If the head touches another part of the body, the length of the snake should reset to 3. This code should be put in the advance() method, which is called everytime the snake moves forward.

for (int i = 1; i<mbody.size; i++) {
    if (mBody.get(i).getX() == mBody.first().getX()
            && mBody.get(i).getY() == mBody.first().getY()) {
        snakeLength = 3;
    }
}

Our mBody Queue uses zero-based indexing. We initialise int i as 1 so that it doesn't check if the xy of the head matches the xy of the head, as this would always be true.

We need to add this just before the if() statement that checks removes the last bodyPart from the Queue so that if the player has 'died' we can remove all extra bodyParts in the queue. We do this first by changing == to >=, because it is now possible for the actual body length to be greater than the desired length (when the snake dies). We then need to change the if statement to a while statement, which will keep removing body parts until the snake reaches length 3. Without the 'while', only one bodyPart would be removed per advance method, which doesn't actually shorten the snake at all.

while (mBody.size - 1 >= snakeLength) {
    mBody.removeLast();
}

Press play and test out the functioning snake game!

Congratulations on the start of your lucrative indie career. From here on out, we'll be adding some touch controls, colours, and then deploying the game to Android.

Step 15: Touch Controls

Let's just implement some simple touch controls using a DPad, which we'll draw onto the screen using rectangles.

Controls

We need to change our update() method in the Controls class to take a Viewport argument. The viewport is needed to convert screen coordinates of a touch to in-game coordinates. Add the Viewport argument:

public void update(Viewport viewport) {

Unfortunately, our viewport is in the GameScreen, but controls.update happens in GameState. An easy way to get around this would be to pass the viewport to our gameState.update(), then pass it down to controls.update().

GameScreen

Add viewport to the gameState.update arguments:

gameState.update(delta, viewport);

GameState

Add viewport to the update arguments, then pass it to controls.update():

public void update(float delta, Viewport viewport) { //update game logic
    mTimer += delta;
    controls.update(viewport);
    if (mTimer > 0.13f) {
        mTimer = 0;
        advance();
    }
}

Controls

Back to the controls class, let's add some touch detection. On a computer, you click the screen to send a touch, but on phones, you touch the screen.

First, add some Vector2 state to Controls:

private Vector2 touch = new Vector2();

This will store the two coordinates of the touch.

We use this code to detect touches:

if (Gdx.input.isTouched()) {    
	touch.x = Gdx.input.getX();
    	touch.y = Gdx.input.getY();
    	viewport.unproject(touch);
}

If the screen is touched, it sets the touch vector to the coordinates of the touch. It then 'unprojects' the touch using the viewport. This just converts the screen coordinates of the phone/computer to the in-game coordinates.

We have touch coordinates, we just need a way to make some buttons. Putting buttons in their own class is probably a good idea for larger projects, but for now, we can just put buttons in the Controls class. We'll describe the buttons as Rectangles, and we can use the Rectangle.contains method to check if the touch Vector is within the Rectangle.

We need four Rectangles, one for each direction. Add this as state:

private Rectangle upBox = new Rectangle(235, 265, 130, 130);
private Rectangle downBox = new Rectangle(235, 5, 130, 130);
private Rectangle leftBox = new Rectangle(65,135,130,130);
private Rectangle rightBox = new Rectangle(365,135,130,130);

Now we just need to check if each of these boxes contains the touch. To do this we just add to our current keyboard-detection if statements. Watch your brackets. Make sure the OR statement is enclosed.

if ((Gdx.input.isKeyPressed(Input.Keys.UP) || upBox.contains(touch))
        && currentDirection != 2) nextDirection = 0;
else if ((Gdx.input.isKeyPressed(Input.Keys.RIGHT) || rightBox.contains(touch))
        && currentDirection != 3) nextDirection = 1;
else if ((Gdx.input.isKeyPressed(Input.Keys.DOWN) || downBox.contains(touch))
        && currentDirection != 0) nextDirection = 2;
else if ((Gdx.input.isKeyPressed(Input.Keys.LEFT) || leftBox.contains(touch))
        && currentDirection != 1) nextDirection =3;

GameState

Finally, we need to draw the buttons using our ShapeRenderer. Add these after the colour has been changed from the background colour:

//buttons

shapeRenderer.rect(235, 265, 130, 135);
shapeRenderer.rect(235, 0, 130, 135);
shapeRenderer.rect(105,135,130,130);
shapeRenderer.rect(365,135,130,130);

(I know these aren't perfectly square but 3 doesn't go into 400 ok)

Now we have a functional game that would run on Android! Run it and make sure you can click the buttons to change direction. Now let's add some colour to the snake.

Step 16: Pretty Colours

Making the snake and controls a little more colourful is pretty simple. All we need to do is make the rgb value depend on the sine function.

GameState

Add this state to GameState:

private float colourCounter = 0;

Then in the update() method we increment the colour counter with delta:

colourCounter += delta;

Finally, in the draw() method we need to change the shapeRenderer colour to depend on the colourCounter:

shapeRenderer.setColor(MathUtils.sin(colourCounter),-MathUtils.sin(colourCounter),1,1);

Play around with this using sin and cos to come up with something you like. Be careful of all three rgb values being 0 at once, because then the snake might go invisible (if your background is black). You can use Math.abs() to turn a negative value into a positive one (because sin and cos go negative half the time). You can also add a scaled amount of delta to colourCounter if you want the colours to switch faster or slower, e.g. colourCounter += 2*delta. Get creative, and remember you can change the colour of the controls and board outline with this method too.

Step 17: Android Settings

There are a couple of things that need to be changed to make the game run properly on a phone.

Screen Orientation:

LibGDX defaults to a landscape orientation but we want portrait. In the file explorer on the left open android->AndroidManifest.xml. Look for the android:screenOrientation line and change it to portrait:

android:screenOrientation="portrait"

Then open src->your.package->AndroidLauncher.java and add this line to hide the status bar and navigation buttons:

config.useImmersiveMode = true;

This should come in-between the initialisation of the config variable and the initialize() method.

Step 18: Test It Out

Before exporting to Android it's a good idea to test it out on a virtual phone. Luckily it's pretty easy to do that from inside IntelliJ itself.

At the top, change the configuration from 'Desktop' to 'Android'. The Android configuration should have automatically been created for you. If it hasn't, follow these steps to create it yourself:

Skip this part if you already have the Android Configuration:

  • Click desktop -> edit configuration
  • Click on the plus icon in the top left, then 'Android App'
  • Name it 'android'
  • Change the module to 'android'

The Android Emulator:

Press the run button while the Android config is selected and you should see a 'Select Deployment Target' window pop up. We need to create a new virtual device, so press that button. You can choose any of these phones you like but I used the Nexus 5. Click next, next, finish, leaving all the options as they are. Now select your chosen phone in the original window and press ok.

You should see a virtual phone appear. If it is landscape, you can use the rotation buttons on the right to change its orientation. It runs a working version of Android, and you can use the touchscreen with the mouse. Give it a short while to load, and your snake game will automatically be installed and launched. Check that the game works as it should, i.e. everything is visible, it's in the right orientation, the buttons work, etc.

If everything is fine, you're ready to export!

Step 19: Exporting

We'll do this from the command line, although there are some Eclipse-specific instructions here. Open up your command line and navigate to the root folder of your project - the folder that contains the android, build, core, desktop, and gradle folders. Now execute the following code to package the project:

./gradlew android:assembleRelease

This will create an unsigned apk, which will work but your phone will need to have APK source checking disabled to install it. Let's sign the app so you and others can install it more easily.

There's a lot of info here about signing. I'll walk you through doing it via the command line.

The simple way to do this is copy across the required files from the Android SDK to your build folder. Open two file explorers. In one of them navigate to yourProject->android->build->outputs->apk. You should see an unsigned apk in there from the previous code. In the other window navigate to the location of the Android SDK. For me this was in users->myUser->Library->android->sdk.

Then navigate to build-tools->27.0.2. In this folder there should be many executables, we're interested in apksigner and zipalign. Copy across the apksigner and zipalign execs, and also copy across the lib folder because the execs depend on it.

Go back to the command line and navigate to the apk folder (android/build/outputs/apk). First we need to align the zip using:

./zipalign -v -p 4 android-release-unsigned.apk android-unsigned-aligned.apk

Run this code and you'll see another apk appear, this one aligned.

To sign the apk we need to create a certificate. This makes sure that if you update the app, you are the real developer because you've signed it with your certificate. Because of this, you need to keep your certificate safe and private.

We can use keytool to generate a certificate. This comes with the Java SDK, so you should just be able to run it from the command line.

keytool -genkey -v -keystore chooseName.jks -keyalg RSA -keysize 2048 -validity 10000 -alias chooseName2

Run this code from the apk folder, replacing chooseName and chooseName2 with names of your choice. chooseName.jks is the file name, and chooseName2 is the alias.

Press enter and answer the questions it asks, setting a strong password. You should see yourKey.jks appear in the apk folder. Now we can sign the apk.

Run this in the command line, changing yourName to the name of the keystore you just generated:

./apksigner sign --ks yourName.jks --out release.apk android-unsigned-aligned.apk

Finally, check that the apk signed properly using:

./apksigner verify release.apk

If nothing happens when you press enter, you're done! You can send the apk to your phone and install it now. You'll need to allow installation from unknown sources in your phone's options menu. This was under the security settings for me.

Step 20: And We Are Done - What Next

The journey was long and arduous, but you now have a working Android app running on your phone. This is just the start. There's much more to learn about LibGDX and much more you can add to this game.

Things to Explore:

  • Textures - using images instead of the ShapeRenderer
  • AssetManager - helps when you have many textures and sounds to keep track of
  • Texture Atlas - improves performance when you have many textures
  • Sounds
  • Menu screens
  • Score and High Score - using 'preferences'
  • Gesture Controls

Download my version of the Snake app on Android here to get an idea of things to add to make this app better.

And there's much more. As a starting point, here are two tutorials you should work through and some helpful pages:

And finally a link to the Official Documentation.

Get creative. Build something cool.

~Keir

Share

    Recommendations

    • Plastics Contest

      Plastics Contest
    • Audio Contest 2018

      Audio Contest 2018
    • Optics Contest

      Optics Contest

    9 Discussions

    0
    None
    ZoltánR6

    Question 2 months ago on Step 2

    My libGDX somehow doesn't work.

    Generating app in C:\Users\Buzivagyok\Desktop\snk

    Executing 'C:\Users\Buzivagyok\Desktop\snk/gradlew.bat clean --no-daemon idea'

    To honour the JVM settings for this build a new JVM will be forked. Please consider using the daemon: https://docs.gradle.org/4.6/userguide/gradle_daem...

    Daemon will be stopped at the end of the build stopping after processing

    WARNING: Configuration 'compile' is obsolete and has been replaced with 'implementation'.

    It will be removed at the end of 2018

    :android:clean UP-TO-DATE

    :core:clean UP-TO-DATE

    :desktop:clean UP-TO-DATE

    :ideaModule

    :ideaProject

    :ideaWorkspace

    :idea

    :android:ideaModule

    :android:idea

    :core:ideaModule

    :core:idea

    :desktop:ideaModule

    :desktop:idea

    Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0.

    See https://docs.gradle.org/4.6/userguide/command_lin...

    BUILD SUCCESSFUL in 1m 25s

    9 actionable tasks: 6 executed, 3 up-to-date

    Done!

    To import in Eclipse: File -> Import -> General -> Existing Projects into Workspace

    To import to Intellij IDEA: File -> Open -> YourProject.ipr

    Any idea?

    7 more answers
    0
    None
    OptimumAllianceZoltánR6

    Answer 8 weeks ago

    Have you tried importing this into the IDE? If you set up the Desktop Application in further steps, does it run and show the badlogic logo?

    0
    None
    ZoltánR6OptimumAlliance

    Reply 8 weeks ago

    No it doesn't. I get an error: WARNING: Configuration 'compile' is obsolete and has been replaced with 'implementation'.

    0
    None
    Penolopy Bulnick

    8 months ago

    The snake game is addictive and I can't believe you did this in 40 minutes!