Chapter 9

Creating an Animated Play Screen

In This Chapter

arrow Implementing simple animations

arrow Using sounds in your game

arrow Handling real-time interactivity

Understanding how to layer images to create the visual effects you want in your game, animate elements on the screen, handle real-time interactivity, and load and play sounds are all critical skills necessary to develop many types of games. This chapter will teach you the basic approach to all these skills and put you well on your way to developing your own game using these effects.

On to the meat of the Whack-a-Mole game!

Handling Images for the Play Screen

You actually want your players to be able to whack those onscreen moles. That’s the point of the game. To do that, you need moles to pop out of holes. Figure 9-1 shows what your mole image looks like.

Figure 9-1: Mole image.

9781118235997-fg0901.eps

Scary, isn’t he? The basic approach you take is to have three layers of graphics: the background, the moles, and masks that the mole images sit behind. To make more sense of this, look at Figure 9-2.

Figure 9-2: Play screen with mole and mask for Hole One.

9781118235997-fg0902.tif

I’ve made the mask transparent in this figure to give you an idea of how things are going to work:

1. The background image will be drawn first.

2. The moles will be drawn on top of the background images.

3. The masks will be drawn on top of the moles, obscuring them from view.

Figure 9-3 is the image you’re using for the real mask, which has the same color and pattern as the grass in the background image.

Figure 9-3: Mask image for obscuring moles.

9781118235997-fg0903.eps

When you animate your moles, modifying their y-position, they’re drawn on the canvas “between” the background and the mask — which makes them appear to rise up out of the holes and pop back into them.

remember.eps Being a game programmer is a bit like being a stage magician. You are often creating pleasant optical illusions for your audience. You have to think about the visual effect that you want the player to experience, and then brainstorm about how you might bring that effect to life. In this case, I decided to use three layers of images to produce the desired visual effect of a mole popping out of a hole, but there’s almost always many ways of accomplishing the same goal.

You’ve already got the background image rendered, so the next thing to draw is your moles. Because you’re rescaling the background image and trying to place your masks and moles relative to the resized image, you need a number of variables to track the scaling for both resizing and placement.

In WhackAMoleView, add the following scaling variables, as well as new bitmaps for your mask and mole graphics.

private int backgroundOrigW;

private int backgroundOrigH;

private float scaleW;

private float scaleH;

private float drawScaleW;

private float drawScaleH;

private Bitmap mask;

private Bitmap mole;

You’ll load your new graphics and set all your scaling variables in ACTION_UP, after the player touches the title screen for the first time.

Modify the ACTION_UP case of your doTouchEvent() method to match Listing 9-1.

/9781118235997-tb0901a.png

/9781118235997-tb0901b.png

Here is a brief explanation of what the various lines do:

7-10 Here you’re just loading in the images for the mask and the mole.

11-12 These lines determine the scaling variables you’ll use to resize images based on how much you scaled the background. You just divide the screen width/height of whatever device you’re on by the original width/height of your background image.

13-16 The createScaledBitmap() method allows us to create new bitmaps for your mask and mole images by multiplying their original width and height by the image scaling factor.

Now that you’ve got the images loaded, how do you know where to draw them? Figure 9-4 shows the x and y values for the seven holes in your game background image.

But these coordinates are for your original image. You’ll also need a set of scaling factors for where to draw images to the screen, based on how much you scaled the background. You also need variables for the x and y position of each of your seven moles.

Figure 9-4: The x and y values for locations of holes on the game screen background.

9781118235997-fg0904.tif

Add the following 16 variable declarations to the rest of your global variables in WhackAMoleView.

private int mole1x, mole2x, mole3x, mole4x, mole5x, mole6x, mole7x;

private int mole1y, mole2y, mole3y, mole4y, mole5y, mole6y, mole7y;

Now you modify the setSurfaceSize() method, since that’s where you capture the screen width and height of the device the game is running on. Modify the setSurfaceSize() method of your WhackAMoleView to match Listing 9-2.

/9781118235997-tb0902a.png

/9781118235997-tb0902b.png

This is pretty straightforward:

7-8 Setting the scaling factors for drawing images to the screen.

9-22 Setting the initial positions for each mole. They don’t line up exactly with the coordinates for the holes, because you want the mole images slightly below the hole position and centered.

The values listed here, once scaled, should work fine for you.

Now you’re ready to draw both your moles and your masks, as shown in Listing 9-3.

Listing 9-3: Modified draw() Method for Drawing Moles and Masks

private void draw(Canvas canvas) {

   try {

     canvas.drawBitmap(backgroundImg, 0, 0, null);

     if (!onTitle) {

        canvas.drawBitmap(mole, mole1x, mole1y, null);

        canvas.drawBitmap(mole, mole2x, mole2y, null);

        canvas.drawBitmap(mole, mole3x, mole3y, null);

        canvas.drawBitmap(mole, mole4x, mole4y, null);

        canvas.drawBitmap(mole, mole5x, mole5y, null);

        canvas.drawBitmap(mole, mole6x, mole6y, null);

        canvas.drawBitmap(mole, mole7x, mole7y, null);

        canvas.drawBitmap(mask, (int) 50*drawScaleW, (int) 450*drawScaleH, null);

        canvas.drawBitmap(mask, (int)150*drawScaleW, (int) 400*drawScaleH, null);

        canvas.drawBitmap(mask, (int)250*drawScaleW, (int) 450*drawScaleH, null);

        canvas.drawBitmap(mask, (int)350*drawScaleW, (int) 400*drawScaleH, null);

        canvas.drawBitmap(mask, (int)450*drawScaleW, (int) 450*drawScaleH, null);

        canvas.drawBitmap(mask, (int)550*drawScaleW, (int) 400*drawScaleH, null);

        canvas.drawBitmap(mask, (int)650*drawScaleW, (int) 450*drawScaleH, null);    

     }

   } catch (Exception e) {

}

Remember that the order in which bitmaps are drawn in this method determines what will be drawn on top or bottom:

1. The background is your first layer, so it needs to be drawn first.

2. Your moles are drawn on top of the background image, using the variables you just initialized.

3. The masks are drawn last, and their coordinates are fixed, since they won’t be moving.

If you run the game, as long as everything is done correctly, it won’t look any different from before! But your new mole and mask images should be there.

tip.eps If you want to check the proper positions of your moles, comment out the lines above that draw the masks and run the game again. You should see the moles in their proper starting positions, ready to pop up and get whacked. Just make sure you include the mask drawing lines back in before you move on.

You jump right into game play when the player touches the title screen. The next section will walk you through how to decide when and where the moles pop up, and how to make them come to life.

ontheweb_modern.eps You can download sample files for this chapter at

http://www.dummies.com/go/androidgameprogramming

Making Simple Animations

Just as with most of your previous functionality, when you add something new, you need some new variables.

Add the following variable declarations to your WhackAMoleView.

private int activeMole = 0;

private boolean moleRising = true;

private boolean moleSinking = false;

private int moleRate = 5;

private boolean moleJustHit = false;

The preceding code keeps track of the movement of moles:

check.png The first variable will keep track of which mole is currently moving. You’ll only have one mole moving at a time.

technicalstuff.eps In an alternate version of the game, you could have multiple moles emerge from their holes at the same time, but quite a few older devices don’t support multi-touch (the capability to capture touch inputs from more than one point on the screen), and you’d really want that capability if you were subjecting your players to multiple moles. So, for now, stick to having them deal with only one mole at a time. The activeMole variable tracks that.

check.png You’ve also got booleans indicating whether a mole is sinking or rising. You’re not handling this with a single variable because sometimes you want all the moles to be neither sinking nor rising. The moleRate variable will handle the speed at which moles are moving. Here’s where you make the game more difficult as it progresses, upping the speed of the moles after every ten successful whacks.

tip.eps You’ll want to experiment with the starting value of this variable:

• If it’s too low, the game may seem boring at first.

• Too high, and it may get too difficult too quickly.

Play around with it and see what works for you.

check.png You’re also adding the moleJustHit variable to track when a player successfully whacks a mole. When you get to user interactions, you’ll begin to use it, but for now you’ll add it in so that you have it ready to use later.

The first new method you’ll add to handle the animation of the moles will be called whenever you need to pick a mole to move. You’ll call it pickActiveMole(). Add this new method to the WhackAMoleThread in your WhackAMoleView and make sure it matches the contents of Listing 9-4.

Listing 9-4: The pickActiveMole() Method

private void pickActiveMole() {

     activeMole = new Random().nextInt(7) + 1;

     moleRising = true;

     moleSinking = false;

}

When this method is called, you set the activeMole variable to a random integer between 1 and 7. You set moleRising to true and moleSinking to false. You’ll update the moleRate variable here later when you start handling whether the user is successfully hitting or missing moles.

You’ll also need the import:

import java.util.Random;

When do you want to call this new method?

check.png When the play screen is launched

check.png When a mole is successfully whacked

check.png When the player misses the mole and it makes it safely back to its initial position in the hole.

Let’s deal with the first case, when the play screen is launched. Add a call to the pickActiveMole() method in the ACTION_UP case of doTouchEvent(), just after the line where you toggle onTitle to false (refer to Listing 9-1 for reference).

onTitle = false;

pickActiveMole();

This gives us an active mole, but it’s still not doing anything! To make those moles pop up, add a new method within the WhackAMoleThread in WhackAMoleView (just after the setRunning() method is fine). Call it animateMoles() and make the contents match Listing 9-5.

Listing 9-5: The animateMoles() Method

private void animateMoles() {

if (activeMole == 1) {

    if (moleRising) {

        mole1y -= moleRate;                    

    } else if (moleSinking) {

        mole1y += moleRate;

    }

    if (mole1y >= (int) (475*drawScaleH) || moleJustHit) {

        mole1y = (int) (475*drawScaleH);

        pickActiveMole();

    }

    if (mole1y <= (int) (300*drawScaleH)) {

        mole1y = (int) (300*drawScaleH);

        moleRising = false;

        moleSinking = true;

    }

}

if (activeMole == 2) {

    if (moleRising) {

        mole2y -= moleRate;                    

    } else if (moleSinking) {

        mole2y += moleRate;

    }

    if (mole2y >= (int) (425*drawScaleH) || moleJustHit) {

        mole2y = (int) (425*drawScaleH);

        pickActiveMole();

    }

    if (mole2y <= (int) (250*drawScaleH)) {

        mole2y = (int) (250*drawScaleH);

        moleRising = false;

        moleSinking = true;

    }

}

if (activeMole == 3) {

    if (moleRising) {

        mole3y -= moleRate;                    

    } else if (moleSinking) {

        mole3y += moleRate;

    }

    if (mole3y >= (int) (475*drawScaleH) || moleJustHit) {

        mole3y = (int) (475*drawScaleH);

        pickActiveMole();

    }

    if (mole3y <= (int) (300*drawScaleH)) {

        mole3y = (int) (300*drawScaleH);

        moleRising = false;

        moleSinking = true;

    }

}

if (activeMole == 4) {

    if (moleRising) {

        mole4y -= moleRate;                    

    } else if (moleSinking) {

        mole4y += moleRate;

    }

    if (mole4y >= (int) (425*drawScaleH) || moleJustHit) {

        mole4y = (int) (425*drawScaleH);

        pickActiveMole();

    }

    if (mole4y <= (int) (250*drawScaleH)) {

        mole4y = (int) (250*drawScaleH);

        moleRising = false;

        moleSinking = true;

    }

}

if (activeMole == 5) {

    if (moleRising) {

        mole5y -= moleRate;                    

    } else if (moleSinking) {

        mole5y += moleRate;

    }

    if (mole5y >= (int) (475*drawScaleH) || moleJustHit) {

        mole5y = (int) (475*drawScaleH);

        pickActiveMole();

    }

    if (mole5y <= (int) (300*drawScaleH)) {

        mole5y = (int) (300*drawScaleH);

        moleRising = false;

        moleSinking = true;

    }

}

if (activeMole == 6) {

    if (moleRising) {

        mole6y -= moleRate;                    

    } else if (moleSinking) {

        mole6y += moleRate;

    }

    if (mole6y >= (int) (425*drawScaleH) || moleJustHit) {

        mole6y = (int) (425*drawScaleH);

        pickActiveMole();

    }

    if (mole6y <= (int) (250*drawScaleH)) {

        mole6y = (int) (250*drawScaleH);

        moleRising = false;

        moleSinking = true;

    }

}

if (activeMole == 7) {

    if (moleRising) {

        mole7y -= moleRate;                    

    } else if (moleSinking) {

        mole7y += moleRate;

    }

    if (mole7y >= (int) (475*drawScaleH) || moleJustHit) {

        mole7y = (int) (475*drawScaleH);

        pickActiveMole();

    }

    if (mole7y <= (int) (300*drawScaleH)) {

        mole7y = (int) (300*drawScaleH);

        moleRising = false;

        moleSinking = true;

    }

}

}

This method is long, but it’s got a lot of repetition, so it’s not that difficult to follow. You’ve got a conditional for each active mole ID, and they’re all doing the same thing:

1. If a mole is moving, it will either be rising (moving up) or sinking (moving down). You’ll use two variables to track those states:

• If moleRising is true, you decrement the y-position of that particular mole, which makes it move toward the top of the screen. Remember that y=0 is at the top of the screen.

• If moleSinking is true, you increment the y-position of the mole, pushing it toward the bottom of the screen.

2. You check to see if the mole has either been hit or returned to its original y-position.

In either of those cases you call pickActiveMole().

3. The final conditional is to check if the mole has reached its highest position on the screen (that is, it has fully popped out of its hole).

If so, you toggle the moleRising and moleSinking variables so that the mole will move toward the bottom of the screen.

You should be able to run the game now and see the moles popping up and returning to their holes, one at a time. Again, play with the mole rate to see what you think is a good starting speed. Next you specify how the game responds when the player touches the screen on a mole out of its hole.

Handling User Interaction

To handle when the player whacks a mole, you need to detect when their finger touches the screen in the region where an active mole is out of its hole. You also want a snazzy graphic to display to give the feeling of whacking a mole some real punch, something like Figure 9-5.

You also want a nice sound for the whack to give that satisfying feedback of hitting a mole on the head, but that’s a topic for the next section.

Figure 9-5: Image to display when the user whacks a mole.

9781118235997-fg0905.tif

For now, you need to load a new bitmap, and you also need a few more variables for detecting an active whack, as well as tracking how many moles the player has hit or missed. Add the following variable declarations to WhackAMoleView.

private Bitmap whack;

private boolean whacking = false;

private int molesWhacked = 0;

private int molesMissed = 0;

You’ll load the whack image when the player transitions to the play screen after touching the title screen. Modify your ACTION_UP case of doTouchEvent() again so that it matches Listing 9-6.

Listing 9-6: The animateMoles() Method

case MotionEvent.ACTION_UP:

    if (onTitle) {

        backgroundImg = BitmapFactory.decodeResource(myContext.getResources(), R.drawable.background);

        backgroundImg = Bitmap.createScaledBitmap(backgroundImg, screenW, screenH, true);

        mask = BitmapFactory.decodeResource(myContext.getResources(), R.drawable.mask);

        mole = BitmapFactory.decodeResource(myContext.getResources(), R.drawable.mole);

        whack = BitmapFactory.decodeResource(myContext.getResources(), R.drawable.whack);

        scaleW = (float) screenW/ (float) backgroundOrigW;

        scaleH = (float) screenH/ (float) backgroundOrigH;

        mask = Bitmap.createScaledBitmap(mask, (int)(mask.getWidth()*scaleW), (int)(mask.getHeight()*scaleH), true);

        mole = Bitmap.createScaledBitmap(mole, (int)(mole.getWidth()*scaleW), (int)(mole.getHeight()*scaleH), true);

        whack = Bitmap.createScaledBitmap(whack, (int)(whack.getWidth()*scaleW), (int)(whack.getHeight()*scaleH), true);

        onTitle = false;

        pickActiveMole();

    }

    whacking = false;

    break;

All the lines do here is load the whack bitmap and set the whacking variable to false. You do this because you only want the graphic to display when the player has a fingertip down on the screen. Whenever the finger lifts, you want the game to stop displaying the graphic.

Now you need the logic for detecting whether the player’s finger is touching the region where an active mole is present. For that, you need two global variables to track where the user touched the screen. Add the following two variables to your WhackAMoleView.

private int fingerX, fingerY;

Add a new method called detectMoleContact() (just after pickActiveMole() is fine). Make sure the contents match Listing 9-7.

Listing 9-7: The detectMoleContact() Method

private boolean detectMoleContact() {

    boolean contact = false;

    if (activeMole == 1 &&

            fingerX >= mole1x &&

            fingerX < mole1x+(int)(88*drawScaleW) &&

            fingerY > mole1y &&

            fingerY < (int) 450*drawScaleH) {

        contact = true;

        moleJustHit = true;

    }

    if (activeMole == 2 &&

            fingerX >= mole2x &&

            fingerX < mole2x+(int)(88*drawScaleW) &&

            fingerY > mole2y &&

            fingerY < (int) 400*drawScaleH) {

        contact = true;

        moleJustHit = true;

    }

    if (activeMole == 3 &&

            fingerX >= mole3x &&

            fingerX < mole3x+(int)(88*drawScaleW) &&

            fingerY > mole3y &&

            fingerY < (int) 450*drawScaleH) {

        contact = true;

        moleJustHit = true;

    }

    if (activeMole == 4 &&

            fingerX >= mole4x &&

            fingerX < mole4x+(int)(88*drawScaleW) &&

            fingerY > mole4y &&

            fingerY < (int) 400*drawScaleH) {

        contact = true;

        moleJustHit = true;

    }

    if (activeMole == 5 &&

            fingerX >= mole5x &&

            fingerX < mole5x+(int)(88*drawScaleW) &&

            fingerY > mole5y &&

            fingerY < (int) 450*drawScaleH) {

        contact = true;

        moleJustHit = true;

    }

    if (activeMole == 6 &&

            fingerX >= mole6x &&

            fingerX < mole6x+(int)(88*drawScaleW) &&

            fingerY > mole6y &&

            fingerY < (int) 400*drawScaleH) {

        contact = true;

        moleJustHit = true;

    }

    if (activeMole == 7 &&

            fingerX >= mole7x &&

            fingerX < mole7x+(int)(88*drawScaleW) &&

            fingerY > mole7y &&

            fingerY < (int) 450*drawScaleH) {

        contact = true;

        moleJustHit = true;

    }

    return contact;

}

This method returns a boolean indicating whether contact was found:

1. For each active mole ID, you check to see whether the coordinates of the finger position are both

• Between the mole’s x-position and its width

• Between its current y-position and the bottom lip of its hole

2. If those conditions are met, you set contact and moleJustHit to true.

Remember that the moleJustHit variable is used in your animateMoles() method to pick a new mole if one has just been whacked.

You call your new animateMoles() method from the run() method of your thread, so that it updates every time the canvas is redrawn. Modify the run() method to match Listing 9-8.

Listing 9-8: Modified run() Method

@Override

public void run() {

    while (running) {

        Canvas c = null;

        try {

            c = mySurfaceHolder.lockCanvas(null);

            synchronized (mySurfaceHolder) {

                animateMoles();

                draw(c);

            }

        } finally {

            if (c != null) {

                mySurfaceHolder.unlockCanvasAndPost(c);

            }

        }

    }

}

Detection needs to take place when the player touches the screen, so you need to modify the ACTION_DOWN case of doTouchEvent(). Modify the case to match Listing 9-9.

Listing 9-9: Modified ACTION_DOWN Case

case MotionEvent.ACTION_DOWN:

    fingerX = X;

    fingerY = Y;

    if (!onTitle && detectMoleContact()) {

        whacking = true;

        molesWhacked++;

    }

    break;

Here you capture the x and y positions of the finger. Then, if you are not on the title screen and your detectMoleContact() method returns true, you set whacking to true and increment the number of moles whacked.

You want to draw the whack image while whacking is true, so add the following few lines to your draw() method. Make sure they’re last, because you want this graphic always drawn on top of everything else:

if (whacking) {

canvas.drawBitmap(whack, fingerX - (whack.getWidth()/2), fingerY -(whack.getHeight()/2), null);

}

By drawing the graphic shifted half the width from the finger position’s x and half the height from the finger position’s y, you center it directly under the finger, giving the illusion of an explosion splaying out from underneath the player’s finger. Give it a try!

The last thing to do before working with sounds is to display the number of whacks and misses. The game isn’t tracking misses yet, so add that functionality to your pickActiveMole() method, as seen in Listing 9-10.

Listing 9-10: Modified pickActiveMole() Method

private void pickActiveMole() {

    if (!moleJustHit && activeMole > 0) {

        molesMissed++;

    }

    activeMole = new Random().nextInt(7) + 1;

    moleRising = true;

    moleSinking = false;

    moleJustHit = false;

    moleRate = 5 + (int)(molesWhacked/10);

}    

You added a few lines at the beginning of this method to increment the molesMissed variable if you’re picking a new mole and one wasn’t just hit and an active mole has been selected.

tip.eps Notice that I also added a line at the end to adjust the rate at which the moles move. Basically this logic increments the speed the moles move by 1 pixel for every 10 moles whacked. Again, depending on how quickly you want the difficulty of the game to ramp up, you can adjust this rate to whatever you think is the most fun.

Now that you’re tracking the number of whacked and missed moles, draw that information to the screen. Remember that to draw text to the canvas you need a Paint object. Let’s draw the text in black, so add the following variable declaration to your WhackAMoleView.

private Paint blackPaint;

Then instantiate it and initialize its parameters by added the following lines at the end of the setSurfaceSize() method.

blackPaint = new Paint();

blackPaint.setAntiAlias(true);

blackPaint.setColor(Color.BLACK);

blackPaint.setStyle(Paint.Style.STROKE);

blackPaint.setTextAlign(Paint.Align.LEFT);

blackPaint.setTextSize(drawScaleW*30);

You’ll need the following imports as well:

import android.graphics.Paint;

import android.graphics.Color;

You’re initializing your Paint parameters here so that you can get the scaling factor to adjust the text size when it’s drawn to the screen. To draw the text, modify your draw() method to include the following two lines before the ones that draw the mole and mask images.

canvas.drawText(“Whacked: “ + Integer.toString(molesWhacked), 10, blackPaint.getTextSize()+10, blackPaint);

 

canvas.drawText(“Missed: “ + Integer.toString(molesMissed), screenW-(int)(200*drawScaleW), blackPaint.getTextSize()+10, blackPaint);

These two lines draw the Whacked and Missed mole counts at the top of the screen. Now that you’ve got all the major game elements drawn, animated, and working the way you want, you’ll add a couple of sounds to enhance the play experience before finishing up the game.

Loading and Playing Sounds

For sound resources, you have a number of options. You can purchase pre-made sound effects, hire a contractor to make them specifically for your game, or make your own. Obviously the last option is the cheapest, but it can also be fun.

tip.eps For your first few games, I’d advise against spending a ton on art and music resources, and while making your own can seem less professional, it’s certainly a safer investment.

The freeware audio editing program Audacity (audacity.sourceforge.net) is a great resource for producing and editing your own sound effects. I used them to generate the two effects you’re using in your game. You’ve got one for a successful whack event and one for a miss.

technicalstuff.eps Android supports a wide range of audio formats but you’re using the Ogg Vorbis (.ogg) format. It has excellent compression quality and tends to have fewer technical issues in Android in my experience. For a comprehensive list, see

http://developer.android.com/guide/appendix/media-formats.html

Sound resources reside in the res/raw directory of your project. If this directory doesn’t exist, create it:

1. Right-click the res directory.

2. Select NewFolder

3. Name the new folder raw.

You can either download the resources for the game via the link provided at the beginning of this chapter, or produce your own. Either way, you need to have two sound files named miss.ogg and whack.ogg in your res/raw directory before you begin modifying the code for sound effects.

There’s an arcane art to making your own sound effects. I kept it pretty simple:

check.png The whack noise was made simply by snapping a piece of paper in front of a microphone (I held both ends of the paper, moved them together, then quickly snapped the sheet flat).

check.png The miss noise is simply a high-pitched “hee-hee” sound produced by voice.

Feel free to purchase or produce your own if you don’t like mine.

You use the SoundPool object in Android to load and play your sounds, so you need to declare more variables at the beginning of your WhackAMoleView.

private static SoundPool sounds;

private static int whackSound;

private static int missSound;

public boolean soundOn = true;

You’ll also need the import statement:

import android.media.SoundPool;

You’ll instantiate the SoundPool and load your sounds from file in the WhackAMoleThread constructor. The final version of your WhackAMoleThread constructor should match Listing 9-11.

/9781118235997-tb0911.png

The SoundPool is instantiated on Line 9, and your two sound effects are loading into the pool via Lines 10 and 11. You want to play the whack sound whenever the player successfully touches a region with an active mole, the same place in the code where you draw the whack graphic, and that’s in your ACTION_DOWN case of doTouchEvent().

The final version of your ACTION_DOWN case should match Listing 9-12.

Listing 9-12: Playing the Whack Sound Effect

case MotionEvent.ACTION_DOWN:

    fingerX = X;

    fingerY = Y;

    if (!onTitle && detectMoleContact()) {

        whacking = true;

        if (soundOn) {

            AudioManager audioManager = (AudioManager) myContext.getSystemService(Context.AUDIO_SERVICE);

            float volume = (float) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

            sounds.play(whackSound, volume, volume, 1, 0, 1);

        }

        molesWhacked++;

    }

    break;

You create an instance of AudioManager to get the volume for the music stream. There are volume levels for other streams, such as the device’s ring tone, but you’ve set up your SoundPool to use the same stream as the one for music, which is the recommended practice for sound effects in games. You get the volume for that stream, then use it to play the sound.

The other sound effect should be played when the player misses a mole and it reaches the bottom of its hole. You can play the sound in your pickActiveMole() method, the same place you check that a mole made it to its minimum height. Modify your pickActiveMole() method to match Listing 9-13.

Listing 9-13: Playing the Miss Sound Effect

private void pickActiveMole() {

    if (!moleJustHit && activeMole > 0) {

       if (soundOn) {

        AudioManager audioManager = (AudioManager) myContext.getSystemService(Context.AUDIO_SERVICE); float volume = (float)

audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

        sounds.play(missSound, volume, volume, 1, 0, 1);

       }

       molesMissed++;

    }

    activeMole = new Random().nextInt(7) + 1;

    moleRising = true;

    moleSinking = false;

    moleJustHit = false;

    moleRate = 5 + (int)(molesWhacked/10);

}

You insert the same logic as you did for playing the whack sound, in the same location where you’re incrementing the molesMissed variable. These additions result in the sound effects playing when you want them to, but there are a couple more things you need to do with regard to sounds.

The first is to enable your game to control the correct volume stream using the device hardware buttons. If you load up the game on a device, you’ll probably notice that if you press the up or down hardware buttons that control volume, by default they adjust the ring volume and not the volume of the sound in your game. To remedy this, you need to tell your game to adjust the music stream volume with the hardware buttons. This is done with a single line of code in the WhackAMoleActivity. Modify the onCreate() method of WhackAMoleActivity to match Listing 9-14.

Listing 9-14: Controlling Volume

@Override

public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    requestWindowFeature(Window.FEATURE_NO_TITLE);

    getWindow().setFlags

         (WindowManager.LayoutParams.FLAG_FULLSCREEN,

         WindowManager.LayoutParams.FLAG_FULLSCREEN);

    setContentView(R.layout.whackamole_layout);

    myWhackAMoleView = (WhackAMoleView)

         findViewById(R.id.mole);

    myWhackAMoleView.setKeepScreenOn(true);

    setVolumeControlStream(AudioManager.STREAM_MUSIC);

}

That last line of code sets the volume control for the hardware buttons to the music stream while your game is running. The last thing you need to handle with regard to sound is enabling and disabling it via your menu option. Modify the onOptionsItemSelected() method in WhackAMoleActivity to match Listing 9-15.

/9781118235997-tb0915.png

All you did was add Lines 7 and 11 to toggle the sound setting in your WhackAMoleView when the user changes the setting in the options menu.

Try it out and see how you like the sounds. Make sure you test toggling the sound from the options menu.

Handling End of Game

The last thing you need to do to make the game playable is to handle the end of game state. Let’s do that when the player misses their fifth mole.

Instead of using the pre-built dialog boxes as in Crazy Eights, here you draw your own custom dialog box onscreen when the game ends, and prompt the player to start a new one. Figure 9-6 shows what your custom dialog box will look like.

Figure 9-6: Custom Game Over dialog box.

9781118235997-fg0906.tif

You need a boolean variable to track when the end-game state is reached and a bitmap for your dialog box, so add the following declarations to your variables in WhackAMoleView.

private boolean gameOver = false;

private Bitmap gameOverDialog;

You’ll detect the game over condition in the pickActiveMole() method, where you increment the number of missed moles. Modify your pickActiveMole() method to match Listing 9-16.

/9781118235997-tb0916.png

You added the check starting at Line 11 to see if the number of missed moles is equal to or greater than 5. If so, you toggle your gameOver variable. You only want to be updating the game state and animating moles if the game is still going, so let’s modify the run() method of your thread to only animate the moles if gameOver is true.

Modify the run() method in WhackAMoleThread to match Listing 9-17.

Listing 9-17: Modified run() Method

@Override

public void run() {

    while (running) {

        Canvas c = null;

        try {

            c = mySurfaceHolder.lockCanvas(null);

            synchronized (mySurfaceHolder) {

                if (!gameOver) {                         →8

                    animateMoles();                            

                }

                draw(c);

            }

        } finally {

            if (c != null) {

                mySurfaceHolder.unlockCanvasAndPost(c);

            }

        }

    }

}

You just added a conditional on Line 8 to call animateMoles() only when gameOver is not true. Next you’ll only check for mole whacks if gameOver is false when the user touches the screen.

Modify the ACTION_DOWN case of doTouchEvent() to match Listing 9-18.

Listing 9-18: Modified ACTION_DOWN for End of Game

case MotionEvent.ACTION_DOWN:

    if (!gameOver) {

        fingerX = X;

        fingerY = Y;

        if (!onTitle && detectMoleContact()) {

           whacking = true;

           if (soundOn) {

           AudioManager audioManager = (AudioManager)

               myContext.getSystemService

               (Context.AUDIO_SERVICE);

               float volume = (float)

               audioManager.getStreamVolume

               (AudioManager.STREAM_MUSIC);

               sounds.play(whackSound, volume, volume,

               1, 0, 1);

            }

            molesWhacked++;

        }                        

    }

    break;                            

Here you just wrapped all the logic in ACTION_DOWN in a conditional so that it only works if gameOver is false. You’ll load your dialog-box graphic along with your other play screen graphics in ACTION_UP. You also add a check to see whether the game has ended. If it has, you reset all the relevant variables and set gameOver to false so the user can play another round.

Listing 9-19: Modified ACTION_UP for End of Game

case MotionEvent.ACTION_UP:

    if (onTitle) {

        backgroundImg = BitmapFactory.decodeResource(myContext.getResources(), R.drawable.background);

        backgroundImg = Bitmap.createScaledBitmap(backgroundImg, screenW, screenH, true);

        mask = BitmapFactory.decodeResource(myContext.getResources(), R.drawable.mask);

        mole = BitmapFactory.decodeResource(myContext.getResources(), R.drawable.mole);

        whack = BitmapFactory.decodeResource(myContext.getResources(), R.drawable.whack);

        gameOverDialog = BitmapFactory.decodeResource(myContext.getResources(), R.drawable.gameover);

        scaleW = (float) screenW/ (float) backgroundOrigW;

        scaleH = (float) screenH/ (float) backgroundOrigH;

        mask = Bitmap.createScaledBitmap(mask, (int)(mask.getWidth()*scaleW), (int)(mask.getHeight()*scaleH), true);

        mole = Bitmap.createScaledBitmap(mole, (int)(mole.getWidth()*scaleW), (int)(mole.getHeight()*scaleH), true);

        whack = Bitmap.createScaledBitmap(whack, (int)(whack.getWidth()*scaleW), (int)(whack.getHeight()*scaleH), true);

        gameOverDialog = Bitmap.createScaledBitmap(gameOverDialog, (int)(gameOverDialog.getWidth()*scaleW), (int)(gameOverDialog.getHeight()*scaleH), true);

        onTitle = false;

        pickActiveMole();

    }

    whacking = false;

    if (gameOver) {

        molesWhacked = 0;

        molesMissed = 0;

        activeMole = 0;

        pickActiveMole();

        gameOver = false;

    }

    break;                        

As with your other images, you load the dialog box when the play screen is viewed for the first time, and you resize it based on your scaling factor. The last few lines you added reset molesWhacked, molesMissed, and activeMole, then call pickActiveMole() and reset gameOver to false.

Next you want to draw the dialog box if gameOver is true. Add the following lines to your draw() method (make sure you add them at the end, so that the game draws the dialog box on top of everything else).

if (gameOver) {

       canvas.drawBitmap(gameOverDialog, (screenW/2) –

         (gameOverDialog.getWidth()/2), (screenH/2) -

         (gameOverDialog.getHeight()/2), null);

}

That should be it! When the player misses the fifth mole, the screen should look like Figure 9-7.

Figure 9-7: Play screen when the game is over.

9781118235997-fg0907.tif

When the player touches the screen, a new game should start, with all the variables reset to zero. You’ve got another playable game now, although there are still lots of improvements that could be made to make the game more enjoyable and user-friendly. For example, the game starts immediately when you transition from either the title screen or the Game Over dialog box. Typically for arcade games (especially fast-paced ones) there will be a “Ready!” message on the screen with a brief pause, a countdown, or both. This gives the player a chance to get ready for the oncoming wave.

tip.eps As an exercise, I’d encourage you to implement such a pre-game pause in Whack-a-Mole, using what you’ve learned so far, and pay attention to how it affects the flow of the game after it’s implemented. Tiny improvements add up. There’s often an impulse to immediately upload your game when it’s in a playable state, but you really should give it to friends and family to test, while paying attention to how they interact with the game. The smoother and more playable your game is, the more fun it will be, and the more people will want to play it.

As the game stands, it’s pretty one-note. Most players, including kids, would get bored fairly quickly. Think of ways you might add variety and complexity to the game. You could add birds flying by in the sky for the player to whack as well, or super-moles that take two whacks to tackle. You could also divide the game into stages, resetting the speed to a lower rate with the next stage. I’m sure you can think up more variations that would carry your simple base game to another level.

But you’re not quite done with Whack-a-Mole yet. Another important thing you’ll probably want to be doing in your games is persisting data. That’s what Chapter 10 is all about.

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

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