Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Java Game Programming For Dummies - W. Holder, D. Bell

.pdf
Скачиваний:
63
Добавлен:
24.05.2014
Размер:
5 Мб
Скачать
☆
s howMaze ( )

Chapter 7: 2-D Maze 165

Finally, you copy the offscreen image to the screen in p a i nt( ) with this statement:

' g.drawimage(offscreenimage, 0, 0, this);

The complete Maze class, including the paintOffscreenImage( ) and d r a wT a r g e t ( ) methods, is included on the CD.

Repainting the maze in a thread-friendly manner

Because Java calls p a i n t ( ) from a different thread than the thread in which your applet runs, you use wa i t ( ) and not i fyAl 1 ( ) to wait for paint( ) to draw the maze. (CD Chapter 2 discusses the AWT Interface thread that calls paint( ) and shows how to use w a i t ( ) and n o t i f y A l 1 ( ) to control the timing of an animation.) This section shows how you use these techniques to control the animation of a generating maze.

First, notice in the preceding section that before exiting, p a i n t ( ) calls noti fyAI 1 ( ) to wake up any threads that are waiting for the maze to repaint itself. The code waiting to wake up is in s h ow M a z e ( ). You call showMaze ( ) to repa i nt( ) the maze and wait for paint( ) to finish. showMaze( ) sleeps long enough after displaying the maze to produce a consistent 30 frames-per-second (fps) animation frame rate. Here is the

method and supporting fields you add to the Maze class:

protected long timer, maxFrameRate = 30L; // 30 fps

protected void showMaze (boolean allDirty) i f (allDirty 1 1 offscreenImage == null)

repaint(); else

repaint(leftOffset + minXdirty * sgWid, topOffset + minYdirty * sgHyt,

sgWid * (maxXdirty - minXdirty + 1) + lineWid, sgHyt * (maxYdirty - minYdirty + 1) + lineHyt):

try I wait(); I catch (InterruptedException e) {} l ong t = System.currentTimeMillis();

if ((timer -= t - (1000L / maxFrameRate)) > 0) try ( Thread.sleep(timer); I

catch (InterruptedException e) II timer = System.currentTimeMillist);

ma ze [ 01 [ 01.
Table 7-2
Field
sgWid,sqHyt
l i neWid,lineHyt
sgLnWid,sqLnHyt
l eftOffset, topOffset

Part II: Up to Speed

Calculating where the pikels go

You draw the maze squares at pixel locations determined by the size of the Maze canvas and the grid dimensions of the maze. The pixel locations are

the same for wall mazes and block mazes, so you place the code to calculatin the pixel offsets and sizes in the Maze class. The drawSquare( ) methods

implemented by the classes that extend Ma ze use these pixel locations to draw the maze squares.

You calculate the pixel values in resetMaze( ). re setMaze( ) adjusts the pixel width and height of each rectangle in the grid to fit the screen size aih the Maze canvas. Maze overrides the reshape( ) method inherited from

C a n v a s to resize the maze whenever the size of the canvas changes. If

r e s h a p e ( ) changes the size of the canvas, it calls r e s e t M a z e ( ) to calcuilm the new size of the grid squares. Figure 7-7 and Table 7-2 show the fields thW

resetMaze( ) calculates.

Figure 7-7: The pixel dimensions of a maze square.

Pixels Values for Drawing the Maze

What It Contains

The width and height of a grid square in pixels. These include the 1 i neWi d and 1 i neHyt, respectively, of the line on one side of the square.

The pixel width and height of the lines separating grid squares. You set these to zero for block mazes.

The pixel width and height of a grid square plus the separating lines.

The pixel offsets to center the displayed maze within the canvas. These are the canvas pixel offsets of the upper-left corner of the square

drawSquare( )

Chapter 7:2-D Maze 16 7

Knowing that block mazes are simple is hat( the battle

The squares in a block maze are simple colored rectangles. The type of maze square determines the color of the rectangle. Figure 7-1 earlier in this chapter shows an example of a block maze. You implement the

method for the B1 ockMaze class like this:

protected void drawSquare (int xx, int yy) I offscr.setColor(maze[xxl[yy1 == WALL ?

Color.gray : Color.white): offscr.fillRect(leftOffset + (xx * sgWid),

topOffset + (yy * sgHyt), sgWid, sgHyt);

The expression 1 e f t 0 f f s e t + ( x x

* s q W i d ) calculates the pixel offset of

the left edge of the maze square and

t o p O f f set + ( yy * s q Hy t) calcu-

lates the top pixel offset of the square.

Displaying a watt maze

Each square in a wall maze has 16 different possible combinations of walls, and drawing one such square takes several steps. The first step is to check whether the square has all the walls set, in which case you treat the square as a solid wall. You draw a solid wall by filling the entire square with black. If the square isn't a solid wall, you start by erasing the square to white and then drawing the four sides and four corners depending on which walls the square has set. Figure 7-8 shows the 8 wall sections that you draw and the 16 resulting wall maze squares.

and

,

~A

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

iqm7-8:

 

 

0

 

 

u

 

F

n

 

 

 

I I!

 

LI

 

I

J

 

7he 8 wall

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

actions

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

F

 

 

-7..

 

 

missulting

 

 

 

 

 

 

 

 

 

 

 

 

 

 

16 wa I I

6

7

 

 

 

 

 

 

 

 

 

 

 

squares

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

L E FT
R I GHT

68 Part II: Up to Speed

Notice that in Figure 7-8 all 16 squares draw all 4 corner sections of the which happens because Wa 1 1 Maze only generates dense mazes with no open areas. If you allow wall mazes with open areas, you only draw a

if the square on the opposite side of a corner that has any walls set that share the corner, or if the corner of the square is on the edge of the m aze

Figure 7-2 earlier in this chapter shows an example of a wall maze.

The complete W a 1 1 M a z e class, including the d r a w S q u a r e ( ) method, is included on the CD.

To reduce the number of f i 1 1 Rect ( ) calls that d rawSq ua re ( ) has to Use i order to draw the maze square, the f i 1 1 Re ct ( ) calls that draw the wall sides draw both corners as well. For example, the following code draws the top wall and both top corners (sections 1, 2, and 3 in Figure 7-8) in the square:

if (top - (sq & TOP) !- 0)

offscr.fillRect(xoff, yoff, sgl.nuid-, LineHyt);

This code also sets the local variable t o p to t r u e if it draws the top co If d raw Squa re ( ) doesn't draw the LEFT side of the square, it checks to determine if it needs to draw the top-left corner of the square. If top is

fal se, drawSq ua re( ) didn't draw either the TOP or LEFT wall, so it draws the corner. You repeat the TOP check for the BOTTOM wall of the square and repeat the check for the wall.

Customizing the appearance of a wall maze

You can change the look of a wall maze by either setting the pixel width and height of the grid squares to different values or by changing the thickness of the lines that define the walls. For example, the figure shows a maze with squares that are 16 pixels wide, 8 pixels tall, and have walls that are 6 pixels wide and 6 pixels tall.

You control the pixel sizes of the squares calling resize(width, height) to set the dimensions of the maze canvas, and calling setDimensions(squaresWide, squa resHi gh ) to set the maze dimensions. To change the default line sizes, you cal

setLineSizes().

MezeApplet

M a z e
p a t h [ ] [

Chapter 7: 2-D Maze 169

Displaying a solution

A maze stores the current solution path in the p a t h [ ] [ ] array. The bytes in I have a bit set for each side of the corresponding square in the

ma ze [ ] [ ] array that has a solution path. The "Solving Mazes" section earlier in this chapter discusses how you set these bits.

You display the solution by drawing each path segment set in p a t h [ ] [ 1.

pa i ntOffscreenImage( ) calls the drawPathSquare( ) method in Maze to

draw a single path square. Because some subclasses of Ma ze may want to calculate maze solutions but not display them, p a i n t O f f s c r e e n I ma g e ( ) only calls d r a w P a t h S q u a r e ( ) if the boolean field d i s p l a y P a t h is set.

d r a w P a t h S q u a r e ( ) uses pixel sizes and offsets that you initialize in

r e s e t M a z e ( ). p W i d and p Hy t contain the pixel width and height of the displayed path. You set pxoff to the pixel offset within a square of the left edge of a vertical path segment and pyoff to the offset of the top edge of a horizontal segment. You declare these fields in like this:

protected int pWid, pHyt, pxoff, pyoff;

You initialize these fields by adding the following code to resetMaze ( ):

i nt pw = sgWid - lineWid;

 

 

pWid = (pw & 1) == 0 ? Math.max(2, (pw » 1)

& -1)

 

Math.max(1. (pw )> 1)

1);

i nt ph = sgHyt - lineHyt;

 

 

pHyt = (ph & 1) _= 0 ? Math.max(2, (ph >> 1)

& -1)

 

Math.max(1, (ph >> 1)

( 1);

pxoff = (sgLnWid -

pWid)

>> 1;

 

pyoff = (sgLnHyt -

pHyt)

>> l;

 

To center the path in the square, the width of the path must be even if the square width is even and odd if the square width is odd. The following instruction calculates the pixel width of a square:

int pw = sgWid - lineWid;

You use the pixel width pw to calculate the path width like this:

pWid = (pw & 1) == 0 ? Math.max(2, (pw >> 1) & -1) Math.max(1, (pw >> 1) 1 1);

( pw & 1) == 0 is true if the path width is even and false if it is odd. You set an even path width approximately half the width of the square and at least two pixels wide by using Math. max (2, ( pw > > 1). You calculate an odd path width at least one pixel wide using Math. max (1, ( pw > > 1) 1 1).

170 Part II: Up to Speed

Figure 7-9 shows what the completed path for a wall maze looks like.

.t-mom

Figure 7-9:

Displaying a

solved wall

maze.

Putting the maze on the screen

You place a Ma ze canvas on the screen by adding it to a container, such as an applet. This section shows how to implement the M a z e A p p 1 e t class to display the maze and how to use threads to animate the generation and solving of multiple mazes simultaneously. In fact, the applet can even sole, maze while it is still generating it. Figure 7-10 shows a block maze being solved while it is still being generated.

Figure 7-10:

Solving a

partially

generated

maze.

Using a thread to animate, generate, and solve a maze

You spawn a thread to animate, generate, or solve the maze. Because each maze or applet can have more than one thread, you create a thread class handle the different thread operations.

Because the only function of the class is to execute a thread,, you extend the M a z e T h r e a d class directly from T h r e a d instead of implementing the Runnabl e interface.

Depending on the s o l v e parameter passed to the M a z e T h r e a d ( ) constructor, the M a z e T h r e a d class either generates or solves the maze. Here is the complete MazeThread class:

M a z e A p p 1 e t

Chapter 7: 2-D Maze

17 1

 

class MazeThread extends Thread { private Maze maze;

private boolean show, solve;

MazeThread (ThreadGroup tg, Maze maze, boolean show, boolean solve)

super(tg, solve ? Solve thread : Generate thread this.maze = maze;

this.show = show; this.solve = solve; start();

public void run () { i f (show)

setPriority(Thread.MIR'PRIORITY + 1); i f (solve)

maze.traverse(show); else

maze.generate(show);

1 // class MazeThread

Notice that if the operation is being animated, the r u n ( ) method sets the thread priority to M I N_P R I 0 R I TY + 1. Lowering the thread priority makes the user input and screen updating more responsive.

Reviewing parameters in the

MazeAppl et class

To make the features of the maze accessible to HyperText Markup Language (HTML), the M a z e A p p l e t class accepts certain HTML parameters. CD Chapter 1 discusses how to pass parameters to an applet from HTML.

accepts the following parameters:

Parameter Name

What It Specifies

LI NEWIDTH

The width and height of the lines (1 i neWi d, 1 i neHyt).

 

You only want to specify line width for wall mazes. The

 

line width defaults to zero for block mazes and one for

 

wall mazes.

MAZEWIDTH

The number of grid squares wide to make the maze. The maze width defaults to 30 squares.

172 Part II: Up to Speed

 

Parameter Name

What It Specifies

MAZEHEIGHT

The number of grid squares tall to make the maze -"ho

 

maze height defaults to 20 squares.

MAZE

The Maze subclass to use. The default maze class

 

"Wa IIMaze".

For example, you add the following applet tag to your HTML document block maze that is 20 squares wide and 15 squares tall:

<applet code=MazeApplet width=242 height=182> <param name= MAZE value= BlockM'aze >

<param name= MAZEWIDTH value= 20"> <param name= MAZEHEIGHT value= 15"> </applet>

This HTML produces a block maze with squares that are 12 pixels wide 12 pixels tall with a 1-pixel border around the maze. M a z e A p p 1 e t derives these pixel dimensions from the HTML tag like this:

square width = (width / MAZEWIDTH) = (242 / 20) = 12 square height = (height / MAZEHEIGHT) = (182 / 15) = 12 leftOffset = (width % MAZEWIDTH) / 2 = (242 % 20) / 2 = 1 topOffset = (height % MAZEHEIGHT) / 2 = (182 % 20) ! 2 1

MazeAppl et generates a new maze when the user clicks the maze with mouse button. If the user holds the Shift key down while clicking the

on the maze and the maze has added the solution square to the maze. =me applet solves the maze. m o u s e Down ( ) spawns a thread to generate or

the maze. (CD Chapter 2 shows how to spawn threads from event handiew', such as mouseDown ( ).)

The Maze Appl et class is included on the CD.

Chapter 8

2-D Sprite Maze

.......00 0x0*0*00.* * .a*0x0$.0*0...&000.0x0

~ This Chapter

Modeling game elements with sprites Managing sprites with a sprite engine Dusplaying and animating sprites

I ecting and handling sprite collisions long game elements some intelligence meting a 2-D maze game using sprites

. a o . 0 s . a . * 0

# a * 0

* 0 0 o *

0 0 0

0 # 0

+ .

 

 

sprite is an arbitrarily-shaped (not necessarily rectangular) graphic object that moves nondestructively across a background. A familiar

example of a sprite is the mouse cursor - it can be any shape, and it moves around the screen without changing the screen background. You use sprites when you want to minimize redrawing the background.

Sprites are most useful for 2-D games - particularly arcade games where you have a background and various objects moving over it. You use sprites to represent game elements that move around the screen, although you can also use sprites for stationary game elements. Game elements can be any object in your game: spaceships, bullets, explosions, little men, obstacles,

walls, vicious blobs of slime, or a plumber named Mario.

Movable sprites contain code to move across a game background - which brings up the questions of where and how to move the sprite - so we show you how to give your sprites enough intelligence to answer these questions. Of course, moving sprites can run into the boundaries of the background and other moving and stationary sprites, so we show you how to detect and resolve collisions when they occur.

In short, this chapter shows how to create, display, animate, and most importantly, keep track of and manage sprites with a sprite engine. Finally, this chapter puts all the sprite stuff together to make a simple game using

sprites and the B 1 o c k M a z e class from Chapter 7.

74 Part II: Up to Speed

Gentleman, Start Your Sprite Engines!

Sprite engine is j ust a fancy term for a data structure that keeps track ot sprites and tells them when to perform certain operations such as draw g moving, or animating. When applied to software, the term engine identid code that stands on it's own and is general enough to be used in a varieft ~0 applications. A well-constructed sprite engine (which of course includes then one we present in this chapter) can be extended and used in many games

A sprite engine manages the sprites in a rectangular play field. You can give your sprite engine all kinds of bells and whistles, but the four primary

of a sprite engine are to

v Maintain a list of all the sprites under its control and their position in the play field

r r v

Draw the sprites from back to front

Move the sprites

Detect and resolve collisions between sprites or between a sprite and the edge of the play field

Actually, detecting and resolving collisions isn't a requirement for a sprite engine. In some of your games, sprites may need to occupy the same space in the play field without triggering a collision. However, for many games, collision detection is the most important service the sprite engine provides so we include it in our list of primary duties. After all, a shoot-'em-up game wouldn't be much fun if the bullets never hit anything.

To leverage the power of object-oriented programming, your Java sprite engine doesn't actually draw or move the sprites; it simply tells the sprites when to move or draw themselves. Because collision detection either involves more than one sprite or involves the sprite and the edge of the PIR field, the sprite engine takes care of detecting collisions. However, the engine just tells the colliding sprites what happened and lets the sprites determine how to resolve the collision. This division of responsibilities makes the code for both the sprites and the sprite engine fairly simple, yet allows you to build games with hundreds of moving and animating game elements that interact with each other.

Implementing a sprite

A sprite has only a few responsibilities:

r It draws itself.

r It updates its state. This usually involves moving and/or animating the sprite, but it could be anything the sprite needs to do periodically.