Android Game Programming Tutorial Adding Touch Support

In the earlier Android Game Programming tutorial, you may have noticed that the Android Game we created had only keyboard input support. In this tutorial, we will add touch support to the MazeMan, the Android Game we created.

The reason for adding touch support to the Android game is that most of the Android devices don’t have a keypad or track pad. Thus we’ll add touch support along with some other capabilities to our maze game in this second installment of Android Game programming tutorial services.

Note that we will not cover all the changes we’ve made to the Android game in this tutorial but we’ll cover the most important parts. If you are familiar with Android Game programming, you should easily understand the changes.

Persisting Game State in Android Game During Rotation

If you try to run the previous version of the game and you start a new game and move the ball a few blocks and then rotate the screen, then the ball’s position is reset i.e. it returns to its starting point. This is because we were not maintaining the state of the game.

To retain the game’s state i.e. the location of the ball, we’ll give the Maze instance to Android during the rotation (the Maze instance stores the location of the ball). We’ll get that instance back in our Game activity so that we can restore the game to its previous state. For this in our Game activity we’ll override the onRetainNonConfigurationInstance method.

Android calls this method when the phone is rotated so that the Activity can maintain its state. This method returns an Object which is retained by Android and provided to us again. We’ll return the Maze instance from this method. In the onCreate method we’ll call the getLastNonConfigurationInstance method of Activity class to get the instance of Maze class which we persisted during the rotation. When the Game activity is called first time, the getLastNonConfigurationInstance method returns null. Lets see the code of Game activity.

public class Game extends Activity {
    Maze maze;
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        this.maze = (Maze)getLastNonConfigurationInstance();
        if(this.maze == null) {
            this.maze = (Maze)extras.get("maze");
        }
        GameView view = new GameView(this);
        view.setMaze(this.maze);
        setContentView(view);
    }
    public Object onRetainNonConfigurationInstance() {
        return this.maze;
    }
}

The important piece of code here is at line 7 and 15. We use the getLastNonConfigurationInstance method to get instance of Maze class. If this method returns null, it means the onCreate method is called first time and not due to a rotation. So we’ll get the Maze instance from the Intent extras.

Adding Touch Support to Move the Ball

Now for the interesting stuff, moving the ball by touching it. First of all to handle the touch even we’ll override the onTouchEvent method in GameView class. This method is called when user presses on the screen and then start dragging something. The method gets a MotionEvent instance as parameter returns a boolean value indicating if the event was handled. In this method we’ll first check if the touch is on the ball, if it is we’ll set a flag variable to true that the user is dragging the ball.

public boolean onTouchEvent(MotionEvent event) {
    float touchX = event.getX();
    float touchY = event.getY();
    int currentX = maze.getCurrentX();
    int currentY = maze.getCurrentY();
    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            //touch gesture started
            if(Math.floor(touchX/totalCellWidth) == currentX &&
                    Math.floor(touchY/totalCellHeight) == currentY) {
                //touch gesture in the cell where the ball is
                dragging = true;
                return true;
            }
            break;
        //more code to follow
    }
    return false;
}

From the MotionEvent object we first get the X and Y coordinates of the touch event. Then we get the current position of the ball from the Maze instance. In the switch we check which type of touch event happened. We are using the getAction() method to get the action so we have to use the ACTION_MASK with a bitwise & operator. From Android 2.2 you can use getActionMasked() method too, then you won’t need the ACTION_MASK. If the action is start of a touch gesture, we check if the touch is in the current cell of the ball. If it is, we set a boolean flag variable (named dragging) to true. In this case we’ve handled the even so we’ll return true from the method. We’ll set the dragging boolean variable to false when user ends the touch gesture. When the user moves his/her finger during while touching the screen, and the dragging flag is true, we’ll check if the user has moved to a nearby cell. If the user has moved his/her finger to a nearby cell of the ball’s current location, we use the Maze class instance to check if the move is valid. This is sort of the same logic we used when we handled key based ball movement. Here’s the rest of the code of onTouchEvent method.

case MotionEvent.ACTION_UP:
    //touch gesture completed
    dragging = false;
    return true;
case MotionEvent.ACTION_MOVE:
    if(dragging) {
        int cellX = (int)Math.floor(touchX/totalCellWidth);
        int cellY = (int)Math.floor(touchY/totalCellHeight);
       
        if((cellX != currentX && cellY == currentY) ||
                (cellY != currentY && cellX == currentX)) {
            //either X or Y changed
            boolean moved = false;
            //check horizontal ball movement
            switch(cellX-currentX) {
            case 1:
                moved = maze.move(Maze.RIGHT);
                break;
            case -1:
                moved = maze.move(Maze.LEFT);
            }
            //check vertical ball movement
            switch(cellY-currentY) {
            case 1:
                moved = maze.move(Maze.DOWN);
                break;
            case -1:
                moved = maze.move(Maze.UP);
            }
            if(moved) {
                //the ball was moved so we'll redraw the view
                invalidate();
                if(maze.isGameComplete()) {
                    //game is finished
                    showFinishDialog();
                }
            }
        }
        return true;
    }

When the touch action is ACTION_UP we set the dragging flag to false. When the touch action is ACTION_MOVE then we check if the touch has moved to a nearby cell, if it did we check if the move is valid in the Maze and invalidate() the view if the ball was moved. If the ball has reached the finishing point, then we show the finish dialog. The code of showing finish dialog when game is finished by keypad or touch is the same so we moved it to showFinishDialog() method. Here’s the code of showFinishDialog() method.

void showFinishDialog() {
    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    builder.setTitle(context.getText(R.string.finished_title));
    LayoutInflater inflater = (LayoutInflater)context.
                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View view = inflater.inflate(R.layout.finish, null);
    builder.setView(view);
    final AlertDialog finishDialog = builder.create();
    View closeButton =view.findViewById(R.id.closeGame);
    closeButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View clicked) {
            if(clicked.getId() == R.id.closeGame) {
                finishDialog.dismiss();
                ((Activity)context).finish();
            }
        }
    });
    finishDialog.show();
}

This is the same code we used in the previous version of the game to show the finish dialog but we’ve moved it to a separate method now.

Adding Preferences to the Game

One of the good features about Android is Preferences or Settings of an application. Android provides a PreferenceActivity class for this purpose. For the MazeMan game we have preferences for background, ball and line colors in the game. First of all we’ll add a few values to the res/values/strings.xml file. These are the new values

<string name="prefs">Settings</string>
<string name="prefs_game">Game Look</string>
<string name="prefs_ball_color">Ball Color</string>
<string name="prefs_ball_color_title">Select a Ball Color</string>
<string name="prefs_wall_color">Line Color</string>
<string name="prefs_wall_color_title">Select a Line Color</string>
<string name="prefs_back_color">Background Color</string>
<string name="prefs_back_color_title">Select a Background Color</string>

Next we’ll create a new file res/values/arrays.xml. In this file we’ll store the different colors available and their hexadecimal codes

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="bg_colors">
        <item>White</item>
        <item>Grey</item>
        <item>Peach</item>
    </string-array>
    <string-array name="bg_color_codes">
        <item>#FFFFFF</item>
        <item>#DDDDDD</item>
        <item>#FFE5B4</item>
    </string-array>
    <string-array name="wall_colors">
        <item>Black</item>
        <item>Dark Blue</item>
        <item>Maroon</item>
    </string-array>
    <string-array name="wall_color_codes">
        <item>#000000</item>
        <item>#000088</item>
        <item>#800000</item>
    </string-array>
    <string-array name="ball_colors">
        <item>Black</item>
        <item>Blue</item>
        <item>Red</item>
    </string-array>
    <string-array name="ball_color_codes">
        <item>#000000</item>
        <item>#0000FF</item>
        <item>#FF0000</item>
    </string-array>
</resources>

As you can see we have three set of color names and color codes for background, ball and lines or walls. Next we’ll create a new file for the preferences layout res/xml/preferences.xml. This file is similar to the other layout files but it contains some special tags used for preferences layouts.

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">
      <PreferenceCategory android:title="@string/prefs_game">
            <ListPreference
                android:key="bgColor"
                android:title="@string/prefs_back_color"
                android:entries="@array/bg_colors"
                android:entryValues="@array/bg_color_codes"
                android:dialogTitle="@string/prefs_back_color_title" />
            <ListPreference
                android:key="wallColor"
                android:title="@string/prefs_wall_color"
                android:entries="@array/wall_colors"
                android:entryValues="@array/wall_color_codes"
                android:dialogTitle="@string/prefs_wall_color_title" />
            <ListPreference
                android:key="ballColor"
                android:title="@string/prefs_ball_color"
                android:entries="@array/ball_colors"
                android:entryValues="@array/ball_color_codes"
                android:dialogTitle="@string/prefs_ball_color_title" />
      </PreferenceCategory>
</PreferenceScreen>

The root tag here is PreferenceScreen. It can contain direct preferences or we can group preferences using the PreferenceCategory tag as we’ve used here. We are only using ListPreference as the only options in our game are to select a color from a list of colors. There are other types of preferences as well like CheckBoxPreference and EditTextPreference. Each preference is given a name to be used in code using key attribute as we’ve used here. Then each preference is given a display name for user using title attribute. You can also give a descriptive text about the preference using summary attribute. For a ListPreference we have to provide it a list of options to be displayed and their corresponding values to be used in code. We use the entries and entryValues attributes respectively for this. When you touch a list preference the list of values is shown in a small dialog, we can provide a title for that dialog using dialogTitle attribute.

We are almost set with our preferences, now to show the preferences we just need to create a new PreferenceActivity class and call it at the click of a button. First lets create the activity class. The PreferenceActivity we’ll use has bare minimum code, this is the complete class

public class AppPreferences extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }
}

We just need to extend PreferenceActivity class and in the onCreate method call the addPreferencesFromResource method with the preferences layout that we created. Android will itself handle showing the preferences and saving them. Now we need to call this activity on a button, so we’ll add a new button to the menu layout and handle its click event. Here’s the code add in menu.xml and Menu.java activity

<Button android:id="@+id/bPrefs"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:padding="5px"
    android:text="@string/prefs" />
public void onCreate(Bundle savedInstanceState) {
    ....
    Button bPrefs = (Button)findViewById(R.id.bPrefs);
    bPrefs.setOnClickListener(this);
    ....
}
public void onClick(View view) {
    switch(view.getId()) {
        case R.id.bPrefs:
            Intent prefs = new Intent(Menu.this,AppPreferences.class);
            startActivity(prefs);
            break;
....

This code is straight forward. Lets move on to the last part of things, how do we retrieve these preferences. We’ll retrieve these preferences in GameView class. For each color we’ll use the default colors from colors.xml if they are not set in preferences. To get the preferences for our application, we’ll call the PreferenceManager.getDefaultSharedPreferences() method. This method takes a Context as argument so we’ll pass it the Activity instance that we receive in the constructor. Here’s the new constructor of our GameView class.

public GameView(Context context) {
    ....
    //get game colors from preferences
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    int bgColor = getResources().getColor(R.color.game_bg);
    String bgColorStr = prefs.getString("bgColor","");
    if(bgColorStr.length() > 0) {
        bgColor = Color.parseColor(bgColorStr);
    }
    int lineColor = getResources().getColor(R.color.line);
    String lineColorStr = prefs.getString("wallColor","");
    if(lineColorStr.length() > 0) {
        lineColor = Color.parseColor(lineColorStr);
    }
    int ballColor = getResources().getColor(R.color.position);
    String ballColorStr = prefs.getString("ballColor","");
    if(ballColorStr.length() > 0) {
        ballColor = Color.parseColor(ballColorStr);
    }
    line = new Paint();
    line.setColor(lineColor);
    ball = new Paint();
    ball.setColor(ballColor);
    background = new Paint();
    background.setColor(bgColor);
    setFocusable(true);
    this.setFocusableInTouchMode(true);
}

The PreferenceManager.getDefaultSharedPreferences() method returns an instance of SharedPreferences class. On the SharedPreferences object we call the getString() method to get a preference value. The first parameter to this method is the preference name or key, and the 2nd parameter is the default value if the preference is not set. Since the default value we’ve provided as a blank string, so we check if the preference value’s length is greater than zero. If it is we use the Color.parseColor() method to parse the String hexadecimal color code into an int color code.

That’s it, we’ve mainly covered touch and preferences in this part of the Android Game Programming tutorial. You can download the complete source of the Android game which includes some other changes to the application as well which you can explore and enjoy.

Download Source Code Android Game With Touch Support

Note: When you open the project you might get an error about “Unable to resolve target ‘android-7′”. To solve this problem you just have to go to the project properties, then from the Android tab select a project build target which will solve the problem.

adding-touch-support-in-android-game-programming-tutorial

Leave a Comment