How To Create an Android Game Android Game Programming Tutorial

If you are a beginner Android programmer and you want to learn how to create an Android Game, then here’s a basic Android Game Programming tutorial for you. In this tutorial we will create a very simple maze based Android game called MazeMan. Here’s the preview of the game,

android-game-running

Note that a basic familiarity with Android programming is assumed while writing this tutorial. If you are not familiar with programming basics of android or Java, then please go through the basic Android programming tutorials before reading this Android Game Programming tutorial.

Copyright: All code in the tutorial is a property of Zoopable and should not be used without prior permissions.

Support: This tutorial is provided as is and no free support is available for it. If you are interested in general Android programming or Android game programming services and consulting, please contact us for availability.

Android Game Code Structure

These are the files in our project. We’ll quickly look at the role of each file:

android-game-programming-directory-structure-128x300

There are five java files i.e. five classes.

  • Menu class is an Activity which is opened when the application is launched. It creates two buttons to start a new game and exit the application.
  • Game class is another activity which is launched when new game button is pressed.
  • GameView is a custom View class which we use to draw the maze and tracks the ball’s movement in the maze.
  • The Maze class is sort of a POJO class which stores information about the maze and the position of the ball.
  • The MazeCreator is sort of our maze factory, we’ve hard coded three mazes into this class.

In the res (resources) we have 3 layout xml files and two xml files to store values. main.xml file contains the layout of the main menu which contains the new and exit buttons. Since our game has three mazes or levels, pressing the new game button opens a small dialog to choose the level, levels.xml contains the layout for that dialog. finish.xml contains the layout for the small dialog that appears when the game is finished. colors.xml contains the color codes for the ball, maze walls and maze background. strings.xml contains the string values for different buttons and labels in the application. Then there is the AndroidManifest.xml which is a core file in Android projects which you must be already familiar with.

Programming The Menus and Dialogs in Android

First lets create an Activity which will create two simple “New Game” and “Exit” buttons. This is the main activity which we’ve named Menu. We’ll also handle the click of the buttons in this class, so we’ll also implement OnClickListener in this class and override the onClick(View view) method. This is the declaration of this activity in the AndroidManifest.xml file

<activity android:name=".Menu"
         android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

In the intent-filter we’ve defined this Activity to be launched when we click on the application from the application list. We’re also using a string app_name in the manifest file. So we’ll add this String along with a couple of other Strings to the strings.xml file.

<string name="app_name">MazeMan</string>
<string name="btn_newgame">New Game</string>
<string name="btn_exit">Exit</string>

The layout for this activity is fairly simple. It uses a LinearLayout with two Button in it. The layout is saved as main.xml. This is the code of the layout,

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center_vertical">
    <Button android:id="@+id/bNew"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:padding="5px"
            android:text="@string/btn_newgame" />
    <Button android:id="@+id/bExit"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:padding="5px"
            android:text="@string/btn_exit" />
</LinearLayout>

We’ve assigned ids to both the buttons which we’ll use to register events on these buttons. We’ll move to the activity now. This is the declaration of our Menu activity

public class Menu extends Activity implements OnClickListener

The Menu class extends the Activity class and implements OnClickListener interface. In the onCreate method of this activity, we’ll set main as the layout and add click listeners to the New Game and Exit buttons.

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    Button newGame = (Button)findViewById(R.id.bNew);
    Button exit = (Button)findViewById(R.id.bExit);
    newGame.setOnClickListener(this);
    exit.setOnClickListener(this);
}

As you can see, we set the activity instance itself, using this reference, as the click listeners for the two buttons (We could’ve used anonymous inner classes here as well but we chose not to). Here is the code that will handle the button clicks,

public void onClick(View view) {
    //check which button was clicked with its id
    switch(view.getId()) {
        case R.id.bExit:
            finish();
            break;
    }
}

When the Exit button is clicked, we’ll finish the Menu activity which will effectively close the application. For the New Game button, we’ll present the user with a list so that the user can select which stage he/she wants to play. Right now our game has only three levels so we’ll present the user with a dialog which has three options for the three levels. Lets see the code of the onClick method again with the dialog implemented.

public void onClick(View view) {
    //check which button was clicked with its id
    switch(view.getId()) {
        case R.id.bExit:
            finish();
            break;
        case R.id.bNew:
            final String[] levels = {"1", "2", "3"};
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("Select Maze");
            builder.setItems(levels, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int item) {
                    //TODO
                }
            });
            AlertDialog alert = builder.create();
            alert.show();
    }
}

Now, as you can see, we are using AlertDialog.Builder to create an alert dialog with three options. The setItems method of AlertDialog.Builder class takes two parameters. The first is the list of items to display and the second is a listener for the item that is clicked by the user. When an item is clicked, the index of the clicked item is passed as the second argument to the onClick method. When the user selects a maze, we’ll start a new Activity that will handle the actual game. In our application, this is implemented in Game class. The maze itself is represented as an instance of Maze class in our application. We’ll pass the Game activity class an instance of the Maze class based on which maze is selected by the user. We have also created a helper class named MazeCreator which takes the maze number as argument and returns a corresponding Maze instance. We’ll go into the details of how the maze is implemented, but for now, this is how the code on the click of the New Game button looks like when we handle selecting one of the three mazes,

case R.id.bNew:
    String[] levels = {"Maze 1", "Maze 2", "Maze 3"};
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle(getString(R.string.levelSelect));
    builder.setItems(levels, new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int item) {
            Intent game = new Intent(Menu.this,Game.class);  //create an Intent to launch the Game Activity
            Maze maze = MazeCreator.getMaze(item+1);         //use helper class for creating the Maze
            game.putExtra("maze", maze);                     //add the maze to the intent which we'll retrieve in the Maze Activity
            startActivity(game);
        }
    });
    AlertDialog alert = builder.create();
    alert.show();

In our code, we created an Intent to launch the Game activity. We pass the Maze instance to this activity as an extra using the putExtra method of the Intent class. The code for Game activity is also very simple. For drawing the maze, we use a custom View named GameView. The Game activity merely creates an instance of the GameView class and sets it as the content view for the activity. Here is the complete code for the Game activity,

public class Game extends Activity {
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        Intent intent = getIntent();
        Bundle extras = intent.getExtras();    //get the intent extras
        Maze maze = (Maze)extras.get("maze");  //retrieve the maze from intent extras
        GameView view = new GameView(this,maze);
        setContentView(view);
    }
}

As you can see, it gets the Maze instance from the Intent, passes it to the GameView and sets the view as the content view. We’ll also add this activity to the manifest file,

<activity android:name=".Game"></activity>

Implementing and Drawing the Maze

Now the pieces of the puzzle that are left are how the maze is implemented and how it is displayed in the GameView. The other class that we didn’t show you i.e. MazeCreator is fairly simple to understand once you understand the maze’s working. The way we’ve implemented our maze is that the maze stores the location of walls in the maze in two 2D boolean arrays. One of the arrays stores vertical walls of the maze and the other stores the horizontal walls of the maze. We’ll take one of our mazes as an example,

android-game-programming-maze-300x300

Now the first boolean array will store the values for the vertical lines of the maze,

maze-vertical-in-android-game-programming-tutorial-300x300

Wherever there is a wall, there will be true value in the boolean array, and where there is no wall, the value will be false. So the values in the vertical lines array would be like this,

maze-vertical2-in-android-game-programming-300x300

So the vertical lines will be represented by an array of size [8][7]. In a similar way, the horizontal lines in the maze will be represented by an array of size [7][8]. Both these arrays are stored in the Maze class

maze-horizontal-in-android-game-programming-tutorial-300x300

A part from these two arrays, the maze stores the X and Y positions of the finishing point in the maze and the current position of the ball in the maze. When the game starts the current position of the ball will be the starting point of the ball. The Maze class also finds out the horizontal and vertical size of the maze (which are basically the size of the two dimensions of the boolean arrays). Maze class also contains one additional boolean variable gameComplete which indicates if the game is over[1]. The game will be over when the ball reaches the finishing point. This is the code for the Maze class,

public class Maze implements Serializable {

    private static final long serialVersionUID = 1L;

    public static final int UP = 0, DOWN = 1, RIGHT = 2, LEFT = 3;

    private boolean[][] verticalLines;
    private boolean[][] horizontalLines;
    private int sizeX, sizeY;         //stores the width and height of the maze
    private int currentX, currentY;   //stores the current location of the ball
    private int finalX, finalY;       //stores the finishing point of the maze
    private boolean gameComplete;
   
    //setters and getters
}

Since we add the Maze instance as an extra in the Intent we used to start the Game activity, so we’ve implemented Serializable interface in this class. Apart from the getters and setters, there is one additional method in this Maze class named move. This method is called when the user presses a key to move the ball. The direction which the user wants the ball to move is passed to this method as an argument. The class also has four static int constants which represent the directions for the ball’s movement[2]. This method has the logic which finds out if the ball can move in the given direction. If the ball can move in the given direction, this method changes the value of currentX or currentY accordingly. If there is a wall in the direction user wants to move or if the ball is on the edge of the maze in direction of the movement, then currentX and currentY are not modified. This method also checks if the game is complete if the ball is moved. This method returns true if the ball was moved and false if it was not moved. This way if the ball was moved the view can update the screen to show the new location of the ball. Here is the code of the move method,

public boolean move(int direction) {
    boolean moved = false;
    if(direction == UP) {
        if(currentY != 0 && !horizontalLines[currentY-1][currentX]) {
            currentY--;
            moved = true;
        }
    }
    if(direction == DOWN) {
        if(currentY != sizeY-1 && !horizontalLines[currentY][currentX]) {
            currentY++;
            moved = true;
        }
    }
    if(direction == RIGHT) {
        if(currentX != sizeX-1 && !verticalLines[currentY][currentX]) {
            currentX++;
            moved = true;
        }
    }
    if(direction == LEFT) {
        if(currentX != 0 && !verticalLines[currentY][currentX-1]) {
            currentX--;
            moved = true;
        }
    }
    if(moved) {
        if(currentX == finalX && currentY == finalY) {
            gameComplete = true;
        }
    }
    return moved;
}

The code is very simple, we check in which direction user wants to move and see if there is a wall in that direction or if it is an edge of the maze. If both are not the case, we move the ball. At last we check if the ball has reached the finishing point and if it has, we set gameComplete to true. Lets quickly look at the The getMaze method of MazeCreator class, which we used in Menu activity. This method merely creates an instance of Maze class and sets its walls, starting position of the ball and finishing point and returns that maze instance.

public static Maze getMaze(int mazeNo) {
    Maze maze = null;
    if(mazeNo == 1) {
        maze = new Maze();
        boolean[][] vLines = new boolean[][]{
                                {true ,false,false,false,true ,false,false},
                                {true ,false,false,true ,false,true ,true },
                                {false,true ,false,false,true ,false,false},
                                {false,true ,true ,false,false,false,true },
                                {true ,false,false,false,true ,true ,false},
                                {false,true ,false,false,true ,false,false},
                                {false,true ,true ,true ,true ,true ,false},
                                {false,false,false,true ,false,false,false}
                             };
        boolean[][] hLines = new boolean[][]{
                                {false,false,true ,true ,false,false,true ,false},
                                {false,false,true ,true ,false,true ,false,false},
                                {true ,true ,false,true ,true ,false,true ,true },
                                {false,false,true ,false,true ,true ,false,false},
                                {false,true ,true ,true ,true ,false,true ,true },
                                {true ,false,false,true ,false,false,true ,false},
                                {false,true ,false,false,false,true ,false,true }
                            };
        maze.setVerticalLines(vLines);
        maze.setHorizontalLines(hLines);
        maze.setStartPosition(0, 0);
        maze.setFinalPosition(7, 7);
    }
    //other mazes
    return maze;
}

The GameView Class

Now there is only one thing left for us to see. Its the GameView class. GameView class extends from View class and override onSizeChanged, onDraw and onKeyDown methods. It has a few fields which will store the total width and height of the screen[3], the width and height of the maze, the width and height of cells, the finishing point of the maze, the Activity object which called this view, the Maze object to display and a few drawing related objects of Paint class. These are the fields in the GameView class

private int width, height, lineWidth;
private int mazeSizeX, mazeSizeY;
float cellWidth, cellHeight;
float totalCellWidth, totalCellHeight;
private int mazeFinishX, mazeFinishY;
private Maze maze;
private Activity context;
private Paint line, red, background;

We could’ve avoided having some fields like mazeSizeX, mazeSizeY, totalCellWidth, totalCellHeight, mazeFinishX and mazeFinishY. But that would mean that we would have to either calculate these values each time we draw on the screen or get these values from the Maze object. Having a local copy of these fields doesn’t take that much memory and saves some processor time so we’ve made a copy of them. Some of these fields we’ll initialize in the constructor, but some fields like width, height etc depends upon the display resolution of the device. The display resolution is not known in the constructor. Android calls the onSizeChanged with the screen size after constructing the view. So we’ll initialize those fields in the onSizeChanged method. We’ll need three color values for the maze, one black for the maze walls, second red for the ball and the finishing point and one grey for the maze background. So we’ll create a file named colors.xml inside res/values which will have this code

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <color name="game_bg">#dddddd</color>
    <color name="line">#000000</color>
    <color name="position">#ff0000</color>
</resources>

Now that we have these values, lets see the code of the constructor of GameView class

public GameView(Context context, Maze maze) {
    super(context);
    this.context = (Activity)context;
    this.maze = maze;
    mazeFinishX = maze.getFinalX();
    mazeFinishY = maze.getFinalY();
    mazeSizeX = maze.getMazeWidth();
    mazeSizeY = maze.getMazeHeight();
    line = new Paint();
    line.setColor(getResources().getColor(R.color.line));
    red = new Paint();
    red.setColor(getResources().getColor(R.color.position));
    background = new Paint();
    background.setColor(getResources().getColor(R.color.game_bg));
    setFocusable(true);
    this.setFocusableInTouchMode(true);
}

The constructor recieves the instance of Game class as first parameter (named context) and the maze as second parameter[4]. We pass the Game instance to the super constructor of View class. We also keep the Game instance in case we need it. We store the Maze instance to draw and we also create a local copy of the finishing point of the maze and the size of the maze. We then initialize the three Paint instances which we’ll use to draw maze background, walls and the ball. Then we give our GameView the ability to grab focus so that the user would be able to press keys to move the ball. The second Paint object i.e. red will also be used to draw “F” for the finishing point, but its text size cannot be decided yet because we don’t know the screen size and thus we don’t know the cell size. Now we move on to the onSizeChanged method. Here we will know the screen size so we’ll also be able to find out the size of each cell.

protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    width = (w < h)?w:h;   //check whether the width or height of the screen is smaller
    height = width;         //for now square mazes
    lineWidth = 1;          //for now 1 pixel wide walls
    cellWidth = (width - ((float)mazeSizeX*lineWidth)) / mazeSizeX;
    totalCellWidth = cellWidth+lineWidth;
    cellHeight = (height - ((float)mazeSizeY*lineWidth)) / mazeSizeY;
    totalCellHeight = cellHeight+lineWidth;
    red.setTextSize(cellHeight*0.75f);
    super.onSizeChanged(w, h, oldw, oldh);
}

The method is passed the new height and width and the old height and width. The last two parameters matter when the view is already rendered and the screen size changes either because the device was rotated or some other reason. We’ll draw on a square surface as our mazes are square so we find whatever is smaller width or height. The width of walls is fixed at 1 pixel for now. The cell width and height is calculated by subtracting the space occupied by the maze walls from the total width/height. Then we divide the resulting value by the number of horizontal/vertical cells in the maze to get the cell width and height. In our case both cell width and height will be the same but having two separate values means relatively less changes in future, if we want to create rectangular mazes. Now that we know the cell size, we can also set the text size of the Paint object to show the letter “F” on the finishing point. At last we call the onSizeChanged method of View class to do any default processing.

After this, its time to override the onDraw method to draw the maze. This method is passed an instance of Canvas class which you can use to draw lines, circles, text etc. If you have worked with Applets, Canvas is similar to the java.awt.Graphics object. Here is the code for the onDraw method,

protected void onDraw(Canvas canvas) {
    //fill in the background
    canvas.drawRect(0, 0, width, height, background);
   
    boolean[][] hLines = maze.getHorizontalLines();
    boolean[][] vLines = maze.getVerticalLines();
    //iterate over the boolean arrays to draw walls
    for(int i = 0; i < mazeSizeX; i++) {
        for(int j = 0; j < mazeSizeY; j++){
            float x = j * totalCellWidth;
            float y = i * totalCellHeight;
            if(j < mazeSizeX - 1 && vLines[i][j]) {
                //we'll draw a vertical line
                canvas.drawLine(x + cellWidth,   //start X
                                y,               //start Y
                                x + cellWidth,   //stop X
                                y + cellHeight,  //stop Y
                                line);
            }
            if(i < mazeSizeY - 1 && hLines[i][j]) {
                //we'll draw a horizontal line
                canvas.drawLine(x,               //startX
                                y + cellHeight,  //startY
                                x + cellWidth,   //stopX
                                y + cellHeight,  //stopY
                                line);
            }
        }
    }
    int currentX = maze.getCurrentX(),currentY = maze.getCurrentY();
    //draw the ball
    canvas.drawCircle((currentX * totalCellWidth)+(cellWidth/2),   //x of center
                      (currentY * totalCellHeight)+(cellWidth/2),  //y of center
                      (cellWidth*0.45f),                           //radius
                      red);
    //draw the finishing point indicator
    canvas.drawText("F",
                    (mazeFinishX * totalCellWidth)+(cellWidth*0.25f),
                    (mazeFinishY * totalCellHeight)+(cellHeight*0.75f),
                    red);
}

In the onDraw method we first draw a rectangle as the maze background. Then we iterate over the boolean arrays in the Maze class which represent the horizontal vertical lines. If the value in those boolean arrays is true, we draw the corresponding vertical or horizontal line. After that we find out the current location of our ball and draw the ball in that cell. At last we indicate the finishing point by drawing the “F” letter at the finishing point. This is all that’s needed to draw the whole maze. Now all that we need to do is handle the ball movements when the user presses arrow keys.

Lets look at the onKeyDown method now to handle the pressing of the arrow keys. The onKeyDown method returns true if the key press was handled otherwise it returns false. We’ll handle only the arrow keys, other keys we’ll not handle.

@Override
public boolean onKeyDown(int keyCode, KeyEvent evt) {
    boolean moved = false;
    switch(keyCode) {
        case KeyEvent.KEYCODE_DPAD_UP:
            moved = maze.move(Maze.UP);
            break;
        case KeyEvent.KEYCODE_DPAD_DOWN:
            moved = maze.move(Maze.DOWN);
            break;
        case KeyEvent.KEYCODE_DPAD_RIGHT:
            moved = maze.move(Maze.RIGHT);
            break;
        case KeyEvent.KEYCODE_DPAD_LEFT:
            moved = maze.move(Maze.LEFT);
            break;
        default:
            return super.onKeyDown(keyCode,evt);
    }
    if(moved) {
        //the ball was moved so we'll redraw the view
        invalidate();
    }
    return true;
}

The onKeyDown method accepts two arguments. We only need to check which key was pressed so we’ll just need the first argument. The second argument provides us extra information like if the Shift key was pressed when this key was pressed. We’ll use a switch case to see which key was pressed. Based on the arrow key which was pressed, we’ll call the move method of Maze object passing it the direction to move the ball. If any key other than the arrow keys is pressed, we’ll pass the processing to the onKeyDown method of View class so that default processing can be done. Now if the ball actually moved due to the keypress, we call invalidate method. This will result in the view to be redrawn i.e. onDraw method will be executed. We are only lacking one thing now, when the ball is moved, we need to check if the ball reached the finish. If the game is completed, we’ll show a dialog box to congratulate the user and end the game. For this we’ll first need three values in strings.xml file. Here are those values,

<string name="finished_title">Congrats</string>
<string name="finished_text">Level Cleared</string>
<string name="finished_button">End Game</string>

We’ll also need to create a small layout for the dialog box. We’ll save it in the file finish.xml in the res/layout folder. Here is the layout for the finish dialog,

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_gravity="center_horizontal">
    <TextView android:text="@string/finished_text"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:paddingLeft="10px"/>
    <Button android:text="@string/finished_button"
            android:id="@+id/closeGame"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"/>
</LinearLayout>

Nothing fancy here too, just a TextView and one Button. Lets modify the code in onKeyDown method to show this dialog when the game is finished,

if(moved) {
    //the ball was moved so we'll redraw the view
    invalidate();
    if(maze.isGameComplete()) {
        //game completed
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(context.getText(R.string.finished_title));
        LayoutInflater inflater = context.getLayoutInflater();
        View view = inflater.inflate(R.layout.finish, null);
        builder.setView(view);
        View closeButton =view.findViewById(R.id.closeGame);
        closeButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View clicked) {
                if(clicked.getId() == R.id.closeGame) {
                    context.finish();
                }
            }
        });
        AlertDialog finishDialog = builder.create();
        finishDialog.show();
    }
}

After invalidating the view, we’ll check the Maze object to see if the game is over. If it is, we’ll create a dialog using AlertDialog.Builder. We set the title of the dialog and use the LayoutInflater to inflate the finish layout. The inflated layout is set as the view of the dialog. We also add a listener on the button in the dialog to end the game when it is clicked.

That’s it, our Android Game Programming tutorial is now complete and we’ve created our first android game, MazeMan. In this part of the tutorial, we’ve only used key based navigation. In the next part of this tutorial we’ll add touch based playing capabilities to the game.

android-game-gameplay

The full source code of the game is available for download as an Eclipse project. Alternatively you can also directly install the apk file of the game in your simulator or mobile device. The apk file is in the bin folder of the downloaded project.

Download Android Game Source Code

Notes

[1]The size variables and the gameComplete are a little redundant as you can easily find these values from the other fields of the class, but we find this approach to be easier to understand.
[2]We could’ve used enums here, but since we are working on a mobile device, we want to keep things simple and fast.
[3]Since our mazes are square, our view draws in a square area as opposed to the whole screen.
[4]Our GameView class doesn’t have a constructor which takes a Context and an AttributeSet as parameter, this means we can’t use the View through XML layouts. But we didn’t create such a constructor as we’re not going to use the GameView through an xml file.

Leave a Comment