Ping Pong Madness – Level (Part 04)

1

Levels are defined on XML files. Each Level has a name and a set of Sequences and each Sequence has a type.

Attributes:

  • name: just for information purposes
  • type: which Sequence will be used

Example 1:

This is a simple level that will have Sequence 1 and then Sequence 2.

<?xml version="1.0" encoding="utf-8"?>
<level name="Example 1">
	<sequence type="1" />
	<sequence type="2" />
</level>

Example 2:

This is a level that will have Sequence 1, then Sequence 2, then two Sequences 1.

<?xml version="1.0" encoding="utf-8"?>
<level name="Example 2">
	<sequence type="1" />
	<sequence type="2" />
	<sequence type="1" />
	<sequence type="1" />
</level>

Level.java

All the Levels are defined on a single class, just like the Sequences and Shots.

Levels have a set of Sequences. This class is responsible to load Level resources and to handle all Level interactions.

package br.com.thiagorosa.pingpongmadness.tutorial.game;

import java.io.IOException;
import java.util.ArrayList;

import org.xmlpull.v1.XmlPullParserException;

import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.XmlResourceParser;
import android.util.Log;
import br.com.thiagorosa.pingpongmadness.tutorial.R;

public class Level {

    // LOGS
    private static final String TAG = "[PPMT]";
    private static final String TAG_NAME = "[Level] ";

    // SEQUENCE TYPES
    public static final int TYPE_NONE = 0;
    public static final int TYPE_TUTORIAL_001 = 1; // sequences 1,2,3 (37)
    protected int levelType = TYPE_NONE;

    // LEVEL MODES
    public static final int MODE_NONE = 0;
    public static final int MODE_NORMAL = 1;
    public static final int MODE_ENDLESS = 2;
    protected int levelMode = MODE_NONE;

    // MOVEMENT PATH
    public ArrayList<Level.Sequences> allLevel = null;
    private boolean isLevelLoaded = false;

    public Level(Resources resources, int type) {
        levelType = type;

        if (isLevelLoaded == false) {
            allLevel = loadLevel(resources, getLevelList(type));
            if (allLevel != null && allLevel.size() > 0) {
                if (allLevel.get(0).getSequenceType() != 0) {
                    levelMode = MODE_NORMAL;
                }
                else {
                    levelMode = MODE_ENDLESS;
                }
            }
            isLevelLoaded = true;
        }
    }

    private int getLevelList(int type) {
        switch (type) {
            case TYPE_TUTORIAL_001:
                return R.xml.level_001;
            default:
                return R.xml.level_001;
        }
    }

    public int getLevelMode() {
        return levelMode;
    }

    public static boolean isTutorial(int level) {
        boolean tutorial = false;
        if (level == TYPE_TUTORIAL_001) {
            tutorial = true;
        }
        return tutorial;
    }

    /*******************************************************************************************
     *******************************************************************************************/

    protected ArrayList<Level.Sequences> loadLevel(Resources resources, int content) {
        ArrayList<Level.Sequences> mSequence = new ArrayList<Level.Sequences>();

        try {
            XmlResourceParser xrp = resources.getXml(content);

            while (xrp.getEventType() != XmlResourceParser.END_DOCUMENT) {
                if (xrp.getEventType() == XmlResourceParser.START_TAG) {
                    String s = xrp.getName();

                    if (s.equals("level")) {
                        // start level tag
                    }
                    else if (s.equals("sequence")) {
                        // start sequence tag

                        int type = xrp.getAttributeIntValue(null, "type", 0);

                        // add sequence to the level
                        mSequence.add(new Sequences(type));
                    }
                }
                else if (xrp.getEventType() == XmlResourceParser.END_TAG) {
                    String s = xrp.getName();
                    if (s.equals("level")) {
                        // end level tag
                    }
                }
                xrp.next();
            }
            xrp.close();
        }
        catch (XmlPullParserException e) {
            Log.e(TAG, TAG_NAME + "error - exception / xmlpullparser " + e);
        }
        catch (IOException e) {
            Log.e(TAG, TAG_NAME + "error - exception / io " + e);
        }
        catch (NotFoundException e) {
            Log.e(TAG, TAG_NAME + "error - exception / notfound " + e);
        }

        return mSequence;
    }

    /*******************************************************************************************
     *******************************************************************************************/

    public class Sequences {
        private int sequenceType = 0;
        private int sequenceStart = 0;
        private int sequenceEnd = 0;
        private int sequenceSeed = 0;
        private int sequenceAcceleration = 0;

        public Sequences(int type) {
            sequenceType = type;
        }

        public Sequences(int start, int end, int seed, int acceleration) {
            sequenceStart = start;
            sequenceEnd = end;
            sequenceSeed = seed;
            sequenceAcceleration = acceleration;
        }

        public int getSequenceType() {
            return sequenceType;
        }

        public int getSequenceStart() {
            return sequenceStart;
        }

        public int getSequenceEnd() {
            return sequenceEnd;
        }

        public int getSequenceSeed() {
            return sequenceSeed;
        }

        public int getSequenceAcceleration() {
            return sequenceAcceleration;
        }

    }

}

level_001.xml

<?xml version="1.0" encoding="utf-8"?>
<level name="Level 001">
	<sequence type="1" />
	<sequence type="2" />
	<sequence type="3" />
</level>

SVN Project:

PingPong Madness Tutorial – Part 04 SVN

APK:

PingPong Madness Tutorial – Part 04.apk

Tutorial:

Ping Pong Madness – Sequence (Part 03)

0

Sequences are defined on XML files. Each Sequence has a name and a set of Shots. Each Shot has a type, a start and an interval.

Attributes:

  • name: just for information purposes
  • type: which Shot will be used
  • start: where the Shot will start vertically (percentage of the screen)
  • interval: time in milliseconds to wait before firing

Example 1:

This is a simple sequence that will fire type 1 shots on the middle of the screen in 1 second intervals

<?xml version="1.0" encoding="utf-8"?>
<sequence name="Sequence 1">
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="50" interval="1000" />
</sequence>

Example 2:

This sequence will fire type 1 shots on different positions in 1 second intervals

<?xml version="1.0" encoding="utf-8"?>
<sequence name="Sequence 2">
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="40" interval="1000" />
	<shot type="1" start="60" interval="1000" />
	<shot type="1" start="30" interval="1000" />
	<shot type="1" start="70" interval="1000" />
	<shot type="1" start="20" interval="1000" />
	<shot type="1" start="80" interval="1000" />
	<shot type="1" start="10" interval="1000" />
	<shot type="1" start="90" interval="1000" />
	<shot type="1" start="50" interval="1000" />
</sequence>

Sequence.java

All the Sequences are defined on a single class. The resources are stored on static variables to improve performance and make them resusable by all instances.

Sequences have a set of shots. This class is responsible to load Sequence resources and to handle all Sequence interactions.

package br.com.thiagorosa.pingpongmadness.tutorial.game;

import java.io.IOException;
import java.util.ArrayList;

import org.xmlpull.v1.XmlPullParserException;

import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.XmlResourceParser;
import android.util.Log;
import br.com.thiagorosa.pingpongmadness.tutorial.R;

public class Sequence {

    // LOGS
    private static final String TAG = "[PPMT]";
    private static final String TAG_NAME = "[Sequence] ";

    // SEQUENCE TYPES
    public static final int TYPE_NONE = 0;
    public static final int TYPE_SEQUENCE_001 = 1; // straight shots in a row (10)
    public static final int TYPE_SEQUENCE_002 = 2; // straight shots alternated (10)
    public static final int TYPE_SEQUENCE_003 = 3; // straight shots sequential (17)
    protected int sequenceType = TYPE_NONE;

    // SEQUENCE SHOTS
    public static ArrayList<Sequence.Shots> sequenceShots001 = null;
    public static ArrayList<Sequence.Shots> sequenceShots002 = null;
    public static ArrayList<Sequence.Shots> sequenceShots003 = null;
    public ArrayList<Sequence.Shots> sequenceAllShots = null;

    public Sequence(Resources res, int type) {
        switch (type) {
            case TYPE_SEQUENCE_001:
                sequenceAllShots = sequenceShots001;
                break;
            case TYPE_SEQUENCE_002:
                sequenceAllShots = sequenceShots002;
                break;
            case TYPE_SEQUENCE_003:
                sequenceAllShots = sequenceShots003;
                break;
            default:
                sequenceAllShots = sequenceShots001;
                break;
        }
        sequenceType = type;
    }

    /*******************************************************************************************
    *******************************************************************************************/

    public static void loadAllShots(Resources res) {
        sequenceShots001 = loadSequence(res, R.xml.sequence_001);
        sequenceShots002 = loadSequence(res, R.xml.sequence_002);
        sequenceShots003 = loadSequence(res, R.xml.sequence_003);
    }

    private static ArrayList<Sequence.Shots> loadSequence(Resources res, int content) {
        ArrayList<Sequence.Shots> mSequence = new ArrayList<Sequence.Shots>();

        try {
            XmlResourceParser xrp = res.getXml(content);

            while (xrp.getEventType() != XmlResourceParser.END_DOCUMENT) {
                if (xrp.getEventType() == XmlResourceParser.START_TAG) {
                    String s = xrp.getName();

                    if (s.equals("sequence")) {
                        // start sequence tag
                    }
                    else if (s.equals("shot")) {
                        // shot tag
                        int type = xrp.getAttributeIntValue(null, "type", 0);
                        int start = xrp.getAttributeIntValue(null, "start", 0);
                        int interval = xrp.getAttributeIntValue(null, "interval", 0);

                        // add shot to the sequence list
                        mSequence.add(new Shots(type, start, interval));
                    }
                }
                else if (xrp.getEventType() == XmlResourceParser.END_TAG) {
                    String s = xrp.getName();
                    if (s.equals("sequence")) {
                        // end sequence tag
                    }
                }
                xrp.next();
            }
            xrp.close();
        }
        catch (XmlPullParserException e) {
            Log.e(TAG, TAG_NAME + "error - exception / xmlpullparser " + e);
        }
        catch (IOException e) {
            Log.e(TAG, TAG_NAME + "error - exception / io " + e);
        }
        catch (NotFoundException e) {
            Log.e(TAG, TAG_NAME + "error - exception / notfound " + e);
        }

        return mSequence;
    }

    /*******************************************************************************************
    *******************************************************************************************/

    public static class Shots {
        private int shotType;
        private int shotStart;
        private int shotInterval;

        public Shots(int type, int start, int interval) {
            shotType = type;
            shotStart = start;
            shotInterval = interval;
        }

        public int getShotType() {
            return shotType;
        }

        public int getShotStart() {
            return shotStart;
        }

        public int getShotInterval() {
            return shotInterval;
        }

    }

}

sequence_001.xml

<?xml version="1.0" encoding="utf-8"?>
<sequence name="Sequence 001">
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="50" interval="500" />
	<shot type="1" start="50" interval="500" />
	<shot type="1" start="50" interval="500" />
	<shot type="1" start="50" interval="500" />
	<shot type="1" start="50" interval="500" />
</sequence>

sequence_002.xml

<?xml version="1.0" encoding="utf-8"?>
<sequence name="Sequence 002">
	<shot type="1" start="50" interval="1000" />
	<shot type="1" start="40" interval="1000" />
	<shot type="1" start="60" interval="1000" />
	<shot type="1" start="30" interval="1000" />
	<shot type="1" start="70" interval="1000" />
	<shot type="1" start="20" interval="1000" />
	<shot type="1" start="80" interval="1000" />
	<shot type="1" start="10" interval="1000" />
	<shot type="1" start="90" interval="1000" />
	<shot type="1" start="50" interval="1000" />
</sequence>

sequence_003.xml

<?xml version="1.0" encoding="utf-8"?>
<sequence name="Sequence 003">
	<shot type="1" start="50" interval="500" />
	<shot type="1" start="40" interval="500" />
	<shot type="1" start="30" interval="500" />
	<shot type="1" start="20" interval="500" />
	<shot type="1" start="10" interval="500" />
	<shot type="1" start="20" interval="500" />
	<shot type="1" start="30" interval="500" />
	<shot type="1" start="40" interval="500" />
	<shot type="1" start="50" interval="500" />
	<shot type="1" start="60" interval="500" />
	<shot type="1" start="70" interval="500" />
	<shot type="1" start="80" interval="500" />
	<shot type="1" start="90" interval="500" />
	<shot type="1" start="80" interval="500" />
	<shot type="1" start="70" interval="500" />
	<shot type="1" start="60" interval="500" />
	<shot type="1" start="50" interval="500" />
</sequence>

SVN Project:

PingPong Madness Tutorial – Part 03 SVN

APK:

PingPong Madness Tutorial – Part 03.apk

Tutorial:

Ping Pong Madness – Shot (Part 02)

2

Shots are defined on XML files. Each Shot have a name, a return speed and a movement. The movement is a set of moves with a start, a horizontal speed and a vertical speed.

Attributes:

  • name: just for information purposes
  • returnSpeed: the speed after the shot is hit
  • start: when the move will start (percentage of the screen, starts with 100)
  • speedX: horizontal speed
  • speedY: vertical speed

Example 1:

This is a simple shot that has a constant speed of 5.

<?xml version="1.0" encoding="utf-8"?>
<shot name="Example 1" returnSpeed="10">
	<movement>
		<move start="100" speedX="5" speedY="0" />
	</movement>
</shot>

Example 2:

This shot starts with a speed of 5 and on the middle of the screen (start=”50″) it increases its speed to 10.

<?xml version="1.0" encoding="utf-8"?>
<shot name="Example 2" returnSpeed="10">
	<movement>
		<move start="100" speedX="5" speedY="0" />
		<move start="50" speedX="10" speedY="0" />
	</movement>
</shot>

Example 3:

This shot starts with a speed of 10 during the first quarter of the screen, then it decreases its speed to 5 and starts to move vertically with a speed of 10. On the middle of the screen it changes its vertical speed to -10. On the last quarter, it stops moving vertically and increases its horizontal speed to 10.

<?xml version="1.0" encoding="utf-8"?>
<shot name="Example 3" returnSpeed="10">
	<movement>
		<move start="100" speedX="10" speedY="0" />
		<move start="75" speedX="5" speedY="10" />
		<move start="50" speedX="5" speedY="-10" />
		<move start="25" speedX="10" speedY="0" />
	</movement>
</shot>

Shot.java

This was my first game and it was done on early 2010. Now I can see a lot of improvements opportunities, so feel free to modify it and use the best coding practices! =)

All the Shots are defined on a single class. The resources are stored on static variables to improve performance and make them resusable by all instances.

Shots have type, movement, state, coordinates, direction and picture. This class is responsible to load Shot resources and to handle all Shot actions. More info about these methods on future lessons!

package br.com.thiagorosa.pingpongmadness.tutorial.game;

import java.io.IOException;
import java.util.ArrayList;

import org.xmlpull.v1.XmlPullParserException;

import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import br.com.thiagorosa.pingpongmadness.tutorial.R;

public class Shot {

    // LOGS
    private static final String TAG = "[PPMT]";
    private static final String TAG_NAME = "[Shot] ";

    // SHOT TYPES
    public static final int TYPE_NONE = 0;
    public static final int TYPE_SHOT_001 = 1; // straight x05 constant
    protected int shotType = TYPE_NONE;

    // SHOT MOVEMENTS
    private static boolean areResourcesLoaded = false;
    public static ArrayList<Shot.Movement> shotMoves001 = null;
    private ArrayList<Shot.Movement> shotAllMoves = null;

    // SHOT PICTURES
    private static Bitmap shotPictureYellow = null;
    private Bitmap shotPicture = null;

    // SHOT STATES
    public static final int STATE_NONE = 0;
    public static final int STATE_VALID = 1;
    public static final int STATE_HIT = 2;
    public static final int STATE_MISS = 3;
    public static final int STATE_INVALID = 4;
    protected int shotState = STATE_NONE;

    // SHOT MOVES
    private Movement move1 = null;
    private Movement move2 = null;
    private int currentMove = 0;
    protected int shotMoves = 0;

    // SHOT COORDINATES
    private int coordX = 0;
    private int coordY = 0;

    // SHOT DIRECTIONS
    public static final int X_DIRECTION_RIGHT = 1;
    public static final int X_DIRECTION_LEFT = -1;
    public static final int Y_DIRECTION_DOWN = 1;
    public static final int Y_DIRECTION_UP = -1;
    private int _xDirection = X_DIRECTION_RIGHT;
    private int _yDirection = Y_DIRECTION_DOWN;
    protected int shotDirection = 1;

    /*******************************************************************************************
    *******************************************************************************************/

    public Shot(Resources res, int type, int startX, int startY) {

        // load moves and picture accordingly
        switch (type) {
            case TYPE_SHOT_001:
                shotAllMoves = shotMoves001;
                shotPicture = shotPictureYellow;
                break;
            default:
                shotAllMoves = shotMoves001;
                shotPicture = shotPictureYellow;
                break;
        }

        // initial setup
        shotState = STATE_VALID;
        shotType = type;

        // initial coordinates
        setCoordX((int) (startX - getGraphic().getWidth() / 2));
        setCoordY((int) (startY - getGraphic().getHeight() / 2));
    }

    public static Shot createShot(Resources res, int type, int startX, int startY) {

        // load resources if they weren't loaded yet
        if (areResourcesLoaded == false) {
            loadResources(res);
        }

        // create a new shot
        return new Shot(res, type, startX, startY);
    }

    /*******************************************************************************************
    *******************************************************************************************/

    public static void loadResources(Resources res) {
        // load all moves from all shots
        shotMoves001 = loadMoves(res, R.xml.shot_001);

        // load all picture from all shots
        shotPictureYellow = BitmapFactory.decodeResource(res, R.drawable.shot_yellow);

        areResourcesLoaded = true;
    }

    private static ArrayList<Movement> loadMoves(Resources res, int content) {
        ArrayList<Movement> mMovement = new ArrayList<Movement>();

        try {
            XmlResourceParser xrp = res.getXml(content);

            while (xrp.getEventType() != XmlResourceParser.END_DOCUMENT) {
                if (xrp.getEventType() == XmlResourceParser.START_TAG) {
                    String s = xrp.getName();

                    if (s.equals("movement")) {
                        // begin movement tag
                    }
                    else if (s.equals("move")) {
                        // move tag
                        int start = xrp.getAttributeIntValue(null, "start", 0);
                        int speedX = xrp.getAttributeIntValue(null, "speedX", 0);
                        int speedY = xrp.getAttributeIntValue(null, "speedY", 0);

                        // create a new movement and add it on the movement list
                        mMovement.add(new Movement(start, speedX, speedY));
                    }
                }
                else if (xrp.getEventType() == XmlResourceParser.END_TAG) {
                    String s = xrp.getName();
                    if (s.equals("movement")) {
                        // end movement tag
                    }
                }
                xrp.next();
            }
            xrp.close();
        }
        catch (XmlPullParserException e) {
            Log.e(TAG, TAG_NAME + "error - exception / xmlpullparser " + e);
        }
        catch (IOException e) {
            Log.e(TAG, TAG_NAME + "error - exception / io " + e);
        }
        catch (NotFoundException e) {
            Log.e(TAG, TAG_NAME + "error - exception / notfound " + e);
        }

        return mMovement;
    }

    /*******************************************************************************************
    *******************************************************************************************/

    private void moveShot(ArrayList<Movement> allMoves, float position, float acceleration, float adjustmentX, float adjustmentY) {
        // check if it is not the last move
        if (currentMove < allMoves.size() - 1) {

            // get the current and next move
            move1 = allMoves.get(currentMove);
            move2 = allMoves.get(currentMove + 1);

            // set the new coordinates for the shot
            setCoordX((int) (getCoordX() - Math.floor(move1.getShotSpeedX() * adjustmentX * acceleration)));
            setCoordY((int) (getCoordY() - Math.floor(move1.getShotSpeedY() * adjustmentY * shotDirection)));

            // check if it is time for the next move
            if (position * 100 < (float) move1.getShotStart() && position * 100 > (float) move2.getShotStart()) {
                // still same move
            }
            else {
                // next move
                currentMove++;
            }
        }
        else {
            // get the current move
            move1 = allMoves.get(currentMove);

            // set the new coordinates for the shot
            setCoordX((int) (getCoordX() - move1.getShotSpeedX() * adjustmentX * acceleration));
            setCoordY((int) (getCoordY() - move1.getShotSpeedY() * adjustmentY * shotDirection));
        }

    }

    public void moveShot(int type, float position, float acceleration, float adjustmentX, float adjustmentY) {
        moveShot(shotAllMoves, position, acceleration, adjustmentX, adjustmentY);
    }

    /*******************************************************************************************
    *******************************************************************************************/

    public static class Movement {
        private int shotStart;
        private int shotSpeedX;
        private int shotSpeedY;

        public Movement(int start, int speedx, int speedy) {
            shotStart = start;
            shotSpeedX = speedx;
            shotSpeedY = speedy;
        }

        public int getShotStart() {
            return shotStart;
        }

        public int getShotSpeedX() {
            return shotSpeedX;
        }

        public int getShotSpeedY() {
            return shotSpeedY;
        }

    }

    /*******************************************************************************************
    *******************************************************************************************/

    public void toggleShotDirection() {
        if (shotDirection == X_DIRECTION_RIGHT) {
            shotDirection = X_DIRECTION_LEFT;
        }
        else {
            shotDirection = X_DIRECTION_RIGHT;
        }
    }

    public void toggleXDirection() {
        if (_xDirection == X_DIRECTION_RIGHT) {
            _xDirection = X_DIRECTION_LEFT;
        }
        else {
            _xDirection = X_DIRECTION_RIGHT;
        }
    }

    public void toggleYDirection() {
        if (_yDirection == Y_DIRECTION_DOWN) {
            _yDirection = Y_DIRECTION_UP;
        }
        else {
            _yDirection = Y_DIRECTION_DOWN;
        }
    }

    public Bitmap getGraphic() {
        return shotPicture;
    }

    public int getHeight() {
        return shotPicture.getHeight();
    }

    public int getXDirection() {
        return _xDirection;
    }

    public int getYDirection() {
        return _yDirection;
    }

    public int getShotState() {
        return shotState;
    }

    public void setShotState(int state) {
        shotState = state;
    }

    public int getLeftEdge() {
        return coordX;
    }

    public int getRightEdge() {
        return coordX + shotPicture.getWidth();
    }

    public int getCoordX() {
        return coordX + shotPicture.getWidth() / 2;
    }

    public void setCoordX(int value) {
        coordX = value - shotPicture.getWidth() / 2;
    }

    public int getTopEdge() {
        return coordY;
    }

    public int getBottomEdge() {
        return coordY + shotPicture.getHeight();
    }

    public int getCoordY() {
        return coordY + shotPicture.getHeight() / 2;
    }

    public void setCoordY(int value) {
        coordY = value - shotPicture.getHeight() / 2;
    }

}

shot_001.xml

<?xml version="1.0" encoding="utf-8"?>
<shot name="Shot 001" returnSpeed="10">
	<movement>
		<move start="100" speedX="5" speedY="0" />
	</movement>
</shot>

If you need more info, use the comment section below!

SVN Project:

PingPong Madness Tutorial – Part 02 SVN

APK:

PingPong Madness Tutorial – Part 02.apk

Tutorial:

Ping Pong Madness – Splash Screen (Part 01)

1

First of all, setup your development workspace and create a new Android Project.

Our first Activity is a Splash Screen (GameSplash) that appears while the game is loading and after a few seconds it will transition to the next screen (GameSelect).

part01_game_splash part01_game_select

GameSplash.java

  • do not listen to any key events
  • do not stay on the activity stack (android:noHistory)
  • transition to the next screen after a few seconds
package br.com.thiagorosa.pingpongmadness.tutorial;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;

public class GameSplash extends Activity {

    // time to wait on the splash screen
    private static final int SPLASH_SCREEN_DELAY = 3000;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.splash);

        new Thread() {
            @Override
            public void run() {
                try {
                    // do any heavy initialization here

                    // wait a few seconds before going to the next screen
                    sleep(SPLASH_SCREEN_DELAY);
                }
                catch (InterruptedException e) {

                }
                catch (Exception e) {

                }
                finally {
                    // start the level selection screen
                    Intent intentSelect = new Intent(GameSplash.this, GameSelect.class);
                    startActivity(intentSelect);
                }
            }
        }.start();

    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // ignore any key press on the splash screen
        return true;
    }

}

splash.xml

  • RelativeLayout with a black background
  • ImageView with the logo and centered in parent
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#FF000000" >

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        android:src="@drawable/logo" />

</RelativeLayout>

GameSelect.java

  • display a simple layout
package br.com.thiagorosa.pingpongmadness.tutorial;

import android.app.Activity;
import android.os.Bundle;

public class GameSelect extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.level_list);
    }

}

level_list.xml

  • RelativeLayout with a black background
  • TextView with a temporary message
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#FF000000" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="This activity will have a list of all the levels available." />

</RelativeLayout>

AndroidManifest.xml

  • minimum sdk version 7
  • prefer an external install location (requires target sdk version 8)
  • activities are always landscape
  • activities are always full screen without title bar
  • activities handle orientation and keyboard changes
  • GameSplash removed from activity stack
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:installLocation="preferExternal"
    package="br.com.thiagorosa.pingpongmadness.tutorial"
    android:versionCode="1"
    android:versionName="Tutorial - Part 01" >

    <uses-sdk
        android:minSdkVersion="7"
        android:targetSdkVersion="8" />

    <application
        android:debuggable="true"
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
        <activity
            android:configChanges="orientation|keyboard|keyboardHidden"
            android:name=".GameSplash"
            android:noHistory="true"
            android:screenOrientation="landscape"
            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:configChanges="orientation|keyboard|keyboardHidden"
            android:name=".GameSelect"
            android:screenOrientation="landscape"
            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
    </application>

</manifest>

How To Setup Your Development Workspace

0

You need to download and install the Android SDK, download Eclipse, download and install the Eclipse ADT.

Links:

Here is a detailed guide from “Talk Android”:
http://www.talkandroid.com/android-sdk-install-guide/

If you need more info use the comments section below!

Ping Pong Madness Tutorial – Creating Android Games

3

I will teach how to create a simple action game and the game will look like the above video. I will not give much details about the android/java basics, so feel free to use the comments section to ask questions. This was my first game, so there is probably a lot of room for improvement.

The game will look like a Ping-Pong, but it will be more fun and exciting than hitting a single boring ball! The game will have three modes, lots of stages and local best scores.

Each part of this tutorial will assume that the previous parts were already done.

Summary:

The game has three main activities: the first one is a splash screen, the second one has some tabs to select the mode and level, the third one is the game itself.

There are three game modes: Tutorial (to learn about new Shots), Level (a predefined number of Sequences, it doesn’t stop if you miss a Shot) and Endless (infinite number of Sequences, but it stops when you miss a Shot).

Each Mode is a set of Levels, which is a set of Sequences, which is a set of Shots.

Tutorial:

If you have questions, use the comments section!

How To Calculate The MD5 Hash

0

As you know, you shouldn’t store passwords and other sensitive data in plain text. A good approach is to store the data’s MD5 hash.

public static String calcMD5(String s) {
    try {
        MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
        digest.update(s.getBytes());
        byte messageDigest[] = digest.digest();

        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < messageDigest.length; i++) {
            byte b = messageDigest[i];
            String hex = Integer.toHexString((int) 0x00FF & b);
            if (hex.length() == 1) {
                sb.append("0");
            }
            sb.append(hex);
        }

        return sb.toString();
    }
    catch (NoSuchAlgorithmException e) {
        // exception
    }

    return "";
}

For example, instead of storing the password 1234 in plain text, you would store 81dc9bdb52d04dc20036dbd8313ed055. In order to authenticate, you generate the MD5 hash from the user input and then you compare both hashes.

String password = "1234";
calcMD5(salt+password);
// the result is 81dc9bdb52d04dc20036dbd8313ed055

To increase security, you should add a salt before generating the MD5 hash. A salt is a small string containing random characters that are not known by the user.

String salt = "D&K@*SghwP>)#JD-"
String password = "1234";
calcMD5(salt+password);
// the result is cfd1bbaeaa39b7e56e20745c7885d7c5

How To Check If App Is Debuggable

2

This is useful to add logs, unlock levels, bypass security checks and other stuff only when the app is debuggable.

This way, you don’t need to remove or comment code when you publish your app.

public static boolean isDebuggable(Context context, String packagename) {
    try {
        ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(packagename, ApplicationInfo.FLAG_DEBUGGABLE);
        if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == ApplicationInfo.FLAG_DEBUGGABLE) {
            return true;
        }
    }
    catch (NameNotFoundException e) {
        // package name not found
    }
    return false;
}
<application android:label="@string/app_name" android:debuggable="true">

REMINDER: when you publish your app, you should always disable the debuggable flag on your manifest!

How To Check If Market Is Installed

0

Some devices don’t have a market application installed, if you try to open it without checking if it exists, you might get a Force Close!

If there is any market application installed on the device, you can:

  • ask for ratings/stars
  • ask for comments
  • redirect users to a paid version
  • advertise other apps

The function below will search for any app capable of “viewing” market links (i.e. Android Market, SlideME Application Manager, etc).

public static boolean hasMarketInstalled(Context context) {
    Intent market = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=dummy"));
    PackageManager manager = context.getPackageManager();
    List<ResolveInfo> list = manager.queryIntentActivities(market, 0);

    if (list != null && list.size() > 0) {
        return true;
    }
    return false;
}

If you wish to check for Android Market specifically, use this function:

public static boolean hasAndroidMarketInstalled(Context context) {
    Intent market = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=dummy"));
    PackageManager manager = context.getPackageManager();
    List<ResolveInfo> list = manager.queryIntentActivities(market, 0);

    if (list != null && list.size() > 0) {
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).activityInfo.packageName.startsWith("com.android.vending") == true) {
                return true;
            }
        }
     }
    return false;
}

How To Change Layout Attributes Automatically

0

This is very useful to change attributes automatically, without having to change them one by one manually.

Examples:

  • change the Color of all TextViews in a Layout
  • change the TypeFace of all TextViews in a Layout
  • change the Background of all Buttons in a Layout
  • change any attribute of any view in a Layout

The example below changes the color of all TextViews in the Layout.

public static void changeColors(View view, int color) {
    if (view != null) {
        // check if this view is an instance of a viewgroup
        if (view instanceof ViewGroup) {
            // iterate through all this viewgroup's childs
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                // continue to change colors (recursion)
                changeColors(((ViewGroup) view).getChildAt(i), color);
            }
        }
        // check if this view is an instance of a textview
        else if (view instanceof TextView) {
            // change the textview color
            ((TextView) view).setTextColor(color);
        }
    }
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main); 

    // change all the textviews' color to white
    final LinearLayout mainLayout = (LinearLayout) findViewById(R.id.background);
    changeColors(mainLayout, android.R.color.white);
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/background" android:orientation="vertical"
	android:layout_width="fill_parent" android:layout_height="fill_parent">

	<TextView android:id="@+id/text1" android:text="text1"
		android:layout_width="wrap_content" android:layout_height="wrap_content" />

	<TextView android:id="@+id/text2" android:text="text2"
		android:layout_width="wrap_content" android:layout_height="wrap_content" />

	<TextView android:id="@+id/text3" android:text="text3"
		android:layout_width="wrap_content" android:layout_height="wrap_content" />

	<TextView android:id="@+id/text4" android:text="text4"
		android:layout_width="wrap_content" android:layout_height="wrap_content" />

	<Button android:id="@+id/send" android:text="Send"
		android:layout_width="wrap_content" android:layout_height="wrap_content" />

</LinearLayout>

You can easily change the function to do anything you want. Use the example source code below and change XXXXXX (i.e. TextView, ImageView, Button) and YYYYYY (i.e. setTextColor, setBackground, setBackgroundColor) to anything you want! You may also need to change this function’s second parameter type (int newValue).

public static void changeAttribute(View view, int newValue) {
    if (view != null) {
        // check if this view is an instance of a viewgroup
        if (view instanceof ViewGroup) {
            // iterate through all this viewgroup's childs
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                // continue to change the attribute (recursion)
                changeAttribute(((ViewGroup) view).getChildAt(i), newValue);
            }
        }
        // check if this view is an instance of a XXXXXX
        else if (view instanceof XXXXXX) {
            // change the YYYYYY value
            ((XXXXXX) view).YYYYYY(newValue);
        }
    }
}

If you can think of more examples, please leave a comment below!

Go to Top