Hour 24. Ball and Paddle Game

The purpose of this hour is to demonstrate how you can quickly and easily make a complete game using the simple Android engine developed over the past few hours of this book. It will also provide a nice break from the space shoot-’em-up style game from the last two hours. This is a single game, developed in one hour, and kept to a minimum on gameplay to show how useful the engine is for other game genres. Because we have barely touched portrait-oriented games in this book, the ball and paddle game will run in portrait mode.

Creating the Ball and Paddle Game

The ball and paddle game is shown in Figure 24.1. This is an example of a game that runs in portrait mode. Because we have spent most of the hours focusing on games in the landscape orientation, this is an interesting contrast.

Image

Figure 24.1. A simple example of the classic ball and paddle game.

Automated Ball Movement

The gameplay is known to most programmers because it dates back to some very old arcade games, notably Atari’s Breakout and Taito’s Arkanoid. One of the key behaviors of this type of game is a ball that follows a rather simple path on the screen, bouncing or rebounding off the edges. To increase the fun factor, the ball will often change its speed or angle after hitting a block or the player’s paddle, which throws some randomness into the game and makes it more interesting. Figure 24.2 shows the basic bouncing ball logic.

Image

Figure 24.2. Keeping a sprite within a specified boundary by “bouncing” off the “walls.”

Based on our Animation class, we can code a behavior that simulates this type of ball movement logic so that it can be automated. The new class is called ReboundBehavior.

/**
 * ReboundBehavior class - derived from Animation
 * Requires game.engine.Engine to build.
 */
package android.program;
import android.graphics.*;
import android.renderscript.Float2;
import game.engine.*;

public class ReboundBehavior extends Animation {
    private RectF p_bounds;
    private Float2 p_velocity;
    private Point p_size;

    public ReboundBehavior(RectF bounds, Point size, Float2 velocity) {
        animating = true;
        p_bounds = bounds;
        p_velocity = velocity;
        p_size = size;
    }

    @Override
    public Float2 adjustPosition(Float2 original) {
        Float2 modified = original;
        modified.x += p_velocity.x;
        modified.y += p_velocity.y;

        if (modified.x < p_bounds.left)
            p_velocity.x *= -1;
        else if (modified.x > p_bounds.right-p_size.x)
            p_velocity.x *= -1;

        if (modified.y < p_bounds.top)
            p_velocity.y *= -1;
        else if (modified.y > p_bounds.bottom-p_size.y)
            p_velocity.y *= -1;

        return modified;
    }
}

The worker method in ReboundBehavior is adjustPosition(). The logic of this method involves checking the position of the sprite against a boundary (passed as a RectF parameter to the class constructor). When the sprite reaches the edge of the boundary, its direction is changed so that it moves in the opposite direction. The two axes, X and Y, are each handled separately.

Automated Paddle Restriction

Another behavior of this sort of game involves bounding the player’s paddle near the bottom of the screen and preventing it from going off either the left or right edge. In some games of this type, the paddle can move only left or right, not up or down. In this example, the paddle can move in both axes but is limited to a small portion of the bottom of the screen. Figure 24.3 illustrates the situation, with the paddle sprite limited to the region shown (where the jointed line represents the paddle’s movement). This behavior was coded a while back, so we can reuse the FenceBehavior class again, which is shown here.

Image

Figure 24.3. “Fencing” a sprite within a certain boundary.

/**
 * FenceBehavior Class
 */
package game.engine;
import android.graphics.RectF;
import android.renderscript.Float2;

public class FenceBehavior extends Animation {
    private RectF p_fence;

    public FenceBehavior(RectF fence) {
        p_fence = fence;
        animating = true;
    }

    @Override
    public Float2 adjustPosition(Float2 original) {
        Float2 modified = original;

        if (modified.x < p_fence.left)
            modified.x = p_fence.left;
        else if (modified.x > p_fence.right)
            modified.x = p_fence.right;
        if (modified.y < p_fence.top)
            modified.y = p_fence.top;
        else if (modified.y > p_fence.bottom)
            modified.y = p_fence.bottom;

        return modified;
    }
}

Although FenceBehavior is similar to ReboundBehavior, velocity is not involved, so it stops the sprite’s movement when it touches one of the four sides of the boundary.

The Ball and Paddle Source Code

Now we’ll go over the source code for the sample game for this hour. First are the imports, global variables, and game startup and initialization. As you can see from the code listing, the layout of the blocks for the “game level” is defined in an integer array called int[] level. Next, we have the game’s constructor and the init() event.


Watch Out

The game should run at about 50 fps with smooth movement of the ball and responsive touch tracking. If the game runs poorly, check running apps for example projects from previous hours (or other Android games) that may still be taking up processor cycles in the background (if they do not respond properly to the pause/resume events).

Also, be sure to launch the game with the device already in portrait orientation so the game doesn’t have to flip. The engine can set the orientation at startup but cannot handle dynamic flipping. If it flips after startup, the frame rate will drop and the game will stutter.


/**
 * H24 Ball and Paddle Game
* Requires game.engine.Engine to build.
 */

package android.program;
import java.util.Random;
import android.graphics.*;
import android.renderscript.*;
import game.engine.*;

public class Game extends game.engine.Engine {
    Canvas canvas=null;
    Paint paint=null;
    Random rand=null;
    Point touch=null;
    int score=0;

    final int PADDLE_ID = 100;
    final float PADDLE_SPEED = 5.0f;
    Sprite paddle=null;

    final int BALL_ID = 200;
    Sprite ball=null;

    final int BLOCK_ID = 300;
    final int BLOCK_WIDTH = 96;
    final int BLOCK_HEIGHT = 48;
    Texture block_image=null;

    final int LEVEL_WIDTH = 8;
    final int LEVEL_HEIGHT = 8;
    final int[] level = {
            1,1,1,1,1,1,1,1,
            2,0,2,2,2,2,0,2,
            3,3,3,3,3,3,3,3,
            4,4,4,4,4,4,4,4,
            5,5,5,5,5,5,5,5,
            6,6,6,6,6,6,6,6,
            7,0,7,7,7,7,0,7,
            8,8,8,8,8,8,8,8 };

    public Game() {
        paint = new Paint();
        paint.setColor(Color.rgb(100,80,40));
        rand = new Random();
    }

    public void init() {
        setScreenOrientation(Engine.ScreenModes.PORTRAIT);
        setFrameRate(60);
    }

The next section of code includes the load() event, which is called by the engine to allow loading of game assets. Of particular interest in this method is how each of the sprites are created and then added to the engine’s entity manager with a call to addToGroup(). You may recall from Hour 20, “Entity Grouping,” that the engine has a LinkedList of Sprites. After a sprite has been added to the list, it is automatically animated, drawn, and updated according to any attached animations or behaviors. Take note of the calls to addToGroup() here to understand more clearly how the game works with so few lines of code.

public void load() {
    int w = getScreenWidth();
    int h = getScreenHeight();

    //load block image
    block_image = new Texture(this);
    if (!block_image.loadFromAsset("blocks.png")) {
        fatalError("Error loading blocks");
    }

    //position blocks for game level
    for (int y = 0; y < LEVEL_HEIGHT; y++) {
        for (int x = 0; x < LEVEL_WIDTH; x++) {
            Sprite block = new Sprite(this, BLOCK_WIDTH,
                BLOCK_HEIGHT, 4);
            block.setTexture(block_image);
            int index = level[y * LEVEL_WIDTH + x]-1;
            block.setFrame(index);
            block.position.x = 10 + x * (BLOCK_WIDTH+1);
            block.position.y = 100 + y * (BLOCK_HEIGHT+4);
            block.setCollidable(true);
            block.setIdentifier(BLOCK_ID);
            addToGroup(block);
        }
    }

    //load paddle image
    paddle = new Sprite(this);
    if (!paddle.getTexture().loadFromAsset("paddle.png")) {
        fatalError("Error loading paddle");
    }

    //init paddle sprite
    paddle.position = new Float2(w/2,h-200);
    RectF rect = new RectF(0,h-250,w-180,h-52);
    //keep paddle near bottom of screen
    paddle.addAnimation(new FenceBehavior(rect));
    paddle.setCollidable(true);
    paddle.setIdentifier(PADDLE_ID);
    addToGroup(paddle);

    //load ball image
    ball = new Sprite(this);
    if (!ball.getTexture().loadFromAsset("ball.png")) {
        fatalError("Error loading ball");
    }

    //init ball sprite
    ball.position = new Float2(200,h-300);
    ball.setVelocity(new Float2(4.0f,-6.0f));
    //keep ball inside screen boundary
    Point size = ball.getSize();
    ball.addAnimation(new ReboundBehavior(
            new RectF(0, 0, w-size.x, h-size.y),
            size, ball.getVelocity()) );
    ball.setCollidable(true);
    ball.setIdentifier(BALL_ID);
    addToGroup(ball);
}

Next up is the draw() event, the update() event, and finally the collision() event, all called from the engine’s threaded loop. Because the blocks, paddle, and ball were added to the internal entity manager, we do not need to manually move or draw any of these objects in the game. So there’s actually more setup code than gameplay code in a simple game like this. In fact, we’re not too far removed from a fully scripted game at this point (using a script language such as Lua, for instance). Collisions in this game are also fairly simple; the same ball-rebounding logic is used when the ball hits either a block or the paddle. The main difference between the two events is that hitting a block adds to the player’s score.

    public void draw() {
        canvas = getCanvas();
        //clear the screen
        canvas.drawPaint(paint);

        //display the score
        drawText("SCORE " + toString(score),0,20);
    }

    public void update() {
        int inputs = getTouchInputs();
        if (inputs > 0) {
            touch = getTouchPoint(0);
            if (touch.y > getScreenHeight()-250) {
                paddle.position.x = touch.x - paddle.getWidth()/2;
                paddle.position.y = touch.y - 50;
            }
        }
    }

    public void collision(Sprite sprite) {
        switch (sprite.getIdentifier()) {
        case BALL_ID:
            Float2 vel = sprite.getVelocity();

            Sprite other = sprite.getOffender();
            switch (other.getIdentifier()) {
            case BLOCK_ID:
                score++;
                other.setAlive(false);
                vel.y *= -1;
                sprite.setVelocity(vel);
                sprite.position.y += vel.y*2;
                break;

            case PADDLE_ID:
                //make sure ball bounces up
                vel.y = -1 * Math.abs(vel.y);
                sprite.setVelocity(vel);
                sprite.position.y += vel.y;
                break;
            }
            break;
        }
    }
}

That is the end of the source code. Remarkably short, isn’t it? Granted, there is only one level to the game and no way to lose—you just keep playing until clearing the level or exiting. But it’s a start, as it was meant to be. The purpose of this project was twofold: first, to show how easy it is to make a game using the code in this book; second, to give you a framework for making your own ball and paddle game. With all the functionality now working, all that is missing is some creative design to give it some flair and improve the fun factor. There also must be a way to lose—by missing the ball, that is. The rest is up to you! Modify this game, enhance it, and begin improving the engine to suit your own Android game development needs in the future.

Summary

That about sums up the entire book! This hour explored the source code for a simple game to show how the Android game engine presented in this book might be used for a variety of games. Although we did not get into OpenGL ES and do any 3D rendering—which would take several hundred more pages just to explain lighting, cameras, shaders, and the like—I believe you now have all the tools and know-how you need to build your own high-performance Android games! When you have finished your first game and put it up on the Google Play marketplace for Android apps, drop me a line! Here is the QR barcode for my website.

Image

Q&A

Q. What are some core improvements you would make to the engine to increase its usefulness for your favorite game genre?

A. Answers will vary.

Q. Do you think the animation and behavior system used in this game is an improvement over coding game logic manually with helper methods in the main code listing or in a class for a game object, such as a Paddle class, for instance?

A. Answers will vary.

Workshop

Quiz

1. What is the name of the behavior class used to move the ball in the ball and paddle game?

2. What is the name of the Engine method used to add a sprite to the entity manager?

3. What is the name of the behavior class used to keep the player’s paddle sprite in a region near the bottom of the screen?

Answers

1. ReboundBehavior

2. addToGroup()

3. FenceBehavior

Activities

You will probably want to see what interesting new gameplay you can add to the ball and paddle game demonstrated this hour. See what you can do with it as a learning exercise. At minimum, you should give the player some “lives,” take away a life when the ball hits the bottom of the screen, and then reset the ball again. You could also give the player an extra life after reaching a certain number of points or by hitting a special block. You might also cause certain blocks to drop power-up items or launch multiple balls or any number of other interesting gameplay improvements.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.240.119