Java Game Programming For Dummies - W. Holder, D. Bell
.pdf
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);
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
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 |
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
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:
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.
