Java Game Programming For Dummies - W. Holder, D. Bell
.pdf
Chapter 5: Sliding Blocks Brain Teaser |
75 |
The puzzle pieces and the puzzle board shown in Figure 5-2 are all constructed in Photoshop using a third-party plug-in called PhotoTools (from Extensis Corporation) that modifies a background texture, in this case a picture of wood grain, to create the look of beveled edges similar to the effect created by the f i 11 3 D Re c t ( ) in the G ra p h i c s class (see CD Chapter 3 for more information). This effect creates a raised look on the pieces, as if they were cut with a routing tool. The effect is reversed to create the recessed look of the rectangular area that holds the pieces. The same effect is also used on the outside edge of the puzzle board, except that the plug-in is set to create a rounded bevel.
You can easily create your own graphics to replace the files provided on the Java Game Programming For Dummies CD by using the included GIF files as templates. You need to construct 10 different piece files to replace the files piece0.gif through piece9.gif. You can also replace the game board by creating your own board.gif file. You can use almost any image editing program that can save files in the GIF format.
iytitat stamp Pads
Using I ma ge in Java is like having a digital stamp pad that you can use to stamp down copies of a picture onto a G r a p h i c s context. In this case, the stamp pad is an I m a g e object, and you stamp it using the d ra w I m a g e ( ) method provided in the G r a p h i c s class. However, before you can call
d ra w I m a g e ( ), you first have to load an image file and create a Java I m a g e object. You can create an Image object by loading files from a Web server or from your hard disk.
To create an I m a g e object, you use a method called g e t I m a g e ( ) that is provided in the A p p 1 e t class. When you call g e t I m a g e ( ), you pass it a URL parameter (Universal Resource Locator, or more simply, Web address) that tells g e t I m a g e ( ) where to find an image file. Usually the image file is located on a Web server, but it can also come from your hard drive if you only need to run your applet on your computer. g e t I m a g e ( ) loads the data from this image file and uses it to construct an I ma ge object.
You can also call get Image ( ) and pass it a URL and a string that specifies the name of an image file. The string is appended to the URL to specify the exact location of the file. This form of g e t I ma g e ( ) can be conveniently used with
two other App I et methods called getCodeBase( ) and get DocumentBase( ).
Calling g e t C od e B a s e ( ) returns a URL that points to the directory on the Web server from which the applet was loaded. Calling g e t D o c u m e n t B a s e ( ) returns a U R L that points to the directory from which the HTML document that created the applet was loaded. So you can easily fetch an Image from the same directory that contains an applet's class files like this:
76 Part II: Up to Speed
After you have an Image, you can draw it to a G r a phi c s context by calling the G r a phi c s method d r a w I m age(). Here's an example of a simple apple that fetches an I m age using get I m age() and then draws the same I m age with drawImage( ):
i mport java.awt.*;
i mport java.applet.Applet;
class Example extends Applet I coffee;
public void init() {
coffee = gettmage(getCodeBase(), coffee.gif );
I
public void paint (Graphics g) I g.drawImage(coffee, 0, 0, null);
I
Choosing GIF or JPEG
You can use getImage( ) tofetch an I mage from a file encoded in the GIF format, but you can also call getImage( ) to fetch a JPEG encoded Image. The code is basically the same in either case; you just pass the name of the image file, whether it be JPEG or GIF. JPEG files let you use images that contain millions of colors, whereas GIF files have a limit of 256 colors. However, JPEG's larger color palette may sometimes be a disadvantage.
Some people may want to play your games on systems that can only display 256 colors. In this case, Java has to convert a J PEG before it can display it. This conversion process, called dithering, can produce a grainy, undesirable result. You are best off testing your games on
a 256-color system to make sure that you like the result. To completely avoid dithering, you need to be careful to create your GIF files using only the 216-color browser-safe palette. ( CD Chapter 4 covers the ins and outs of the browser-safe palette.)
If you use Adobe Photoshop to create your GIF files, you can convert any type of image to the 216-color browser-safe palette. If you are starting with a JPEG file and wantto convert it to a GIF file, simply select Image-:>Modee I ndexed Color. Then in the dialog box that appears, set the palette option to Web. You can also let Photoshop predither the image by selecting something other than None for the Dither option in the same dialog box.
Chapter 5: Sliding Blocks Brain Teaser |
7 7 |
In this code, d r a w I ma g e ( ) gets passed four parameters. The first parameter is the reference to the I m age object you want d r a w I m age() to draw. The next two parameters are the x and y offset that tell d r a w I m a g e ( ) where to position the upper-left corner of the I m a g e. In this example, the I m a g e is drawn exactly in the upper-left corner at 0,0 (the origin) of the applet's screen area.
The last parameter (n u l l) passed to d r a w I m a g e ( ) is used to pass a reference to an I mageObserver. ImageObserver is an interface you can implement in your applet if you want the applet to be notified of the status of an I ma g e while it is downloading. However, you don't need this capability for the applet in this chapter and can simply pass n u 1 1 when you call
drawImage().
Drawing while downloading
When you try the previous applet, you may notice a strange thing, depending on how fast your browser or applet viewer loads images. When you call g et I ma ge ( ), it returns almost immediately, passing back a reference to an Image object. However, the Image hasn't actually been loaded at this point and doesn't start to load until the first time d r a w I m a g e ( ) is called. To make things even stranger, d r a w I ma g e () attempts to draw an Image even when the isn't fully loaded.
According to the developers of Java, Java's incremental display of images as they are loaded is a "feature," not a bug. The intent is to let you duplicate the effect of a browser displaying an image while it is still loading it. However, for many game applications this can be a real nuisance.
One way to solve this problem is to use the I m a g e O b s e r v e r interface, mentioned earlier in this chapter, to write code that keeps track of the status of the images you are fetching and determines when all of them have been fully loaded. An even simpler solution is to use Java's Medi a T r a c k e r class to manage image loading and keep track of when the images are loaded, as the next section explains.
Loading images with Medi aTracker
The Medi aTracker class lets you construct a list of images you have requested with get Image ( ). After this list is complete, you can tell
Medi aTracker to start loading all of them and to wait until all the images have been loaded before proceeding.
78 Part II: Up to Speed
The first step in using Media Tracker is to create anew Media Tracker object, like this:
MediaTracker tracker = new MediaTracker(this);
The t h i s parameter passed to the M e d i a T r a c k e r constructor is a referenot to the applet that needs to use the images. M e d i a T r a c k e r uses this parameter to register with the code that actually loads the images.
MediaTracker.addImage()
After you have a new M e d i a T r a c k e r object, you can start making a list of images for M e d i a T r a c k e r to manage. You add an image to the list by calling MediaTracker's a d d I ma g e ( ) method. Here's an example:
Image boardImage = getlmage(getCodeBaseC), board.gif );
tracker.addlmage(boardImage, board.gif ), 0);
Notice that you still need to call g e t I m a g e ( ) to request the I m a g e you wauL However, you then also call a d d I m a g e ( ) to add the I m a g e to the list of images your M e d i a T r a c k e r is managing.
When you call a d d I m a g e ( ), you are required to give it an i n t parameter that specifies an ID value for the I m a g e you are telling M e d i a T r a c k e r to track. M e d i a T r a c k e r supports several different methods for monitoring image loading. For example, the M e d i a T r a c k e r method w a i t F o r I D ( ) waits for all images that were assigned a particular ID value to load before it returns. However, it's usually easier to call the M e d i a T r a c k e r method
1 ( ), which doesn't return until all the images you added with add Image ( ) are loaded. If you use wai t ForAl 1 ( ), it doesn't matter what ID value you pass to
MediaTracker.waitForAIIO
After you add alI the images to your M e d i a T r a c k e r object, you call the method wa i t For AI 1 ( ) to start loading the images and wait for loading to finish. This call doesn't return until all the images you added with
a d d I m a g e ( ) are fully loaded and ready to use. However, the wai t F o rA l 1 ( ) method has one more detail you have to handle.
The method specifies that it can throw an
I n t e r r u p t e d E x c e p t i o n. This exception isn't currently implemented in Java 1.0.2, but you still need to provide a t ry/c a t c h block so that the code compiles properly, like this:
try f tracker.waitForAll();
I catch (InterruptedException e
Chapter 5: Sliding Blocks Brain Teaser |
79 |
|
Loading multiple images
The sliding blocks puzzle has 11 different images that you need to load - 10 puzzle pieces plus the puzzle board. The image for the puzzle board is called board.gif; the images for the puzzle pieces are named piece0.gif through piece9.gif. You can simplify the code to load the puzzle pieces by taking advantage of the sequential naming of the puzzle piece files. Code a loop and then use string concatenation (fancy lingo meaning to combine multiple strings into one) to create the name of each file from the loop counter variable. For example, if you have an i n t variable called i i and the current value of i i is zero, the following code creates the string pi ece0 .
String frame = piece + ii + gif ;
Using this string concatenation trick, here is the code you need to load the puzzle board and all 10 puzzle pieces:
MediaTracker tracker = new MediaTracker(this); boardImage = getImage(getCodeBase(), board.gif ); tracker.addlmage(boardImage, 0);
for(intii=0;ii<10;ii++){
piecelmages[ii] = getImage(getCodeBase(),
piece + ii + gif ); tracker.addImage(piecelmages[ii], 0);
try' I |
|
tracker.waitForAll(); |
|
I catch (InterruptedException e) I |
} |
You can put this code in the i n i t ( ) method of the puzzle applet. You also need to declare b o a r d I m a g e and the p i e c e s [ ] array as class variables, like this:
private Image |
offscreenImage, boardImage; |
private Image[] |
pieceImages = new Image[10]; |
Lr~r~iaq Out the Game t3oard
The design of the puzzle board, shown in Figure 5-3, aligns all the puzzle pieces onto an invisible 4 x 5 grid to make calculating how to draw each puzzle piece onto the board as easy as possible. The origin (upper-left corner) of the grid is offset from the applet's origin by the values specified in the variables g r i d X and g r i d Y. The width and height of a grid square is specified by the variables pi eceWi dth and pi eceHei ght.
so Part II: Up to Speed
Figure 5-3: All the puzzle pieces on the game board align to an i nvisible
4x5grid centered in the middle of the board.
gridX I |
pieceWidth |
|
|
gridY |
|
|
|
pieceHeight |
(0,0) |
(2,0) |
(3,0) |
(0,2) |
( 2,2) |
(3,2) |
(0,3) |
(2,3) |
(3,3) |
(0,4) |
( 2,4) |
(3,4) |
The grid squares are arranged such that grid square x=0, y=0 is the upperleft square, and x=3, y=4 is the lower-right square, as shown in Figure 5-3. When you draw a puzzle piece onto the board, you calculate the upper-left corner for any grid square from these four variables, g r i d X, g r i dY,
pi eceWi dt h, and pi eceHei ght, using the following formulas:
imageX = gridX x pieceWidth + gridX imageY = gridY x pieceHeight + gridY
However, you can't apply these formulas until you know the proper values
to assign to gridX, gridY, pieceWidth, and pi eceHei ght. You can write
the code to initialize these values from constants, but this means you can' change the size of the puzzle graphics without recompiling the code. Instead, because the size of the grid square is equal to the size of the smallest puzzle piece, you can initialize p i e c e W i d t h and p i e c e H e i g h t by reading the width and height of one of the puzzle piece images.
Chapter 5: Sliding Blocks Brain Teaser
Reading the width and height of an Image
The Image class contains methods called getWi dth( ) and get Height() that determine the width and height of an Image. For example, to read the width and height of the game board I mage board Image, use the following code:
int wi dt^ - boardImage.getWidth(na113;
i nt height = boardlmage.getHeight(null);
Due to the design of the Java AWT, you can't reliably read an Image's width and height until the Image has been fully loaded. This isn't a problem when you use M e d i a T r a c k e r to wait for an I mage to fully load before you use it - a very good reason to always use M e d i a T r a c k e r to load your images.
Initializing gri dX, gri dY, pi eceWi dth, and pieceHeight
Using getWi dth( ) and get Height() lets you initialize pieceWidth and
p i e c e H e i g h t by reading the width and height of one of the small puzzle piece images. The earlier section "Loading multiple images" shows code that loads all the puzzle piece image files and saves references to the Image objects in an Image[] array called pieceImages. pieceImages [51 contains a reference to the image file piece5.gif, which is one of the small puzzle pieces. Read the width and height of p i e c e I m a g e s [ 51 to initialize
pi eceWi dth and pi eceHei ght, like this:
nt pieceWidth - pieceImages[5],getWidth(null); int pieceHeight = pieceTmages[5l.getHeight(null);
You still haven't initialized g r i d X or g r i d Y, but you can easily calculate these values from the width and height of the game board image
boardImage and pi eceWi dth and pi eceHei ght. Because the grid is
centered in the middle of the game board, you calculate g r i d X by subtracting the width of the grid from the width of the game board (b o a r d I m a g e) and then divide the result by two. The width of the grid is 4 x pi eceWi dth, so you calculate g r i d X like this:
ridX = ( boardlmage.getWidth(null) - ( pieceWidth * 4)) / 2;
You calculate g r i d Y using nearly identical code:
:•idY = (boardlmage.getHeight(null) -
(pieceHeight * 5)) / 2;
S2 Part II: Up to Speed
Craftinly the Puzzle
To make the puzzle work, you need to construct a class to encapsulate the logic that handles each individual puzzle piece. This new class, called
P i e c e, needs to contain a constructor to instantiate (create and define) the pieces needed by the puzzle, code to draw the I mage that represents the piece on the board, and code to handle sliding the piece from place to platy-.
on the board.
Making puzzle pieces that act like real puzzle pieces
One of the trickiest aspects of coding the puzzle is designing the logic that makes the puzzle pieces act like real puzzle pieces placed on a real board_ For example, when you try to slide a puzzle in the direction of an adjacent piece, the adjacent piece either blocks the first piece from moving, or, if tha piece is able to slide in the same direction, is pushed along.
To accomplish this, each puzzle piece needs to know its size and current position and be able to check the size and position of the other pieces. In addition, you need to have some way to monitor when one piece pushes another piece.
Thankfully, the Java AWT includes a built-in class called Re c t a n g 1 e that is designed to represent a movable rectangular area. Re c t a n g 1 e includes cod that can check whether one rectangular area overlaps or intersects another rectangular area and greatly simplifies your task of creating the logic of
sliding puzzle pieces. Re c t a n g 1 e's built-in features make it the perfect superclass for your new Pi ece class.
Starting with Rectangl e, you can easily code the beginnings of your new Pi ece class, including code to draw the image of the puzzle piece, like this
class Piece extends Rectangle { private I mage pic;
Piece (int bx, int by, Rectangle grid, Image img) i super(bx * grid.width + grid.x,
by * grid.height + grid.y,
i mg.getWidth(null), img.getHeight(null)); pic = img;
|
|
Chapter 5: Sliding Blocks Brain Teaser |
83 |
|
;blic void draw (Graphics g) |
|
|
||
g.drawImage(pic, x, y, null); |
|
|
||
The first two parameters to Pi ece, bx and by , specify the location of the |
|
|||
piece on the invisible 4 x 5 grid shown in Figure 5-3. For example, to position |
|
|||
a piece in the upper-left position on the grid, specify bx = 0 and by = 0. |
|
|||
Even though you specify the location of a piece on the grid, the piece can't |
|
|||
calculate where to draw the Image that represents the piece unless it knows |
|
|||
the values you computed for gri dX, gri dY, pi eceWi dth, and pHyt. You could |
|
|||
pass these parameters to the constructor for P i e c e in four i n t parameters. |
|
|||
However, it's easier to create a Re c t a n g 1 e object from these four values and |
|
|||
pass a reference to this object to P i e c e in a parameter called g r i d. |
|
|||
The last parameter for P i e c e, i m g, is a reference to the I m a g e that repre- |
|
|||
sents the piece on the board. The constructor saves this reference in the |
|
|||
variable p i c, which is used by P i e c e's d r a w ( ) method. The constructor |
|
|||
calls i mg . getWi dth( ) and i mg . getHei ght( ) to determine the size of the |
|
|||
I m a g e and passes this to the superclass constructor, along with the pixel |
|
|||
position. Therefore, after a piece is instantiated, Pi ece's x and y values |
|
|||
record the pixel position of the piece on the board, and the P i e c e's w i d t h |
|
|||
and h e i g h t values give the real size of the P i e c e, in pixels. |
|
|||
P i e c e's d r a w ( ) method, as shown in the code in this section, uses the |
|
|||
I m a g e variable p i c and the x and y pixel position values to draw the image |
|
|||
that represents the piece onto the game board. |
|
|
||
Putting the pieces together |
|
|
||
Now that you can instantiate a puzzle piece, you're ready to finish the |
|
|||
applet's i |
n i t ( ) method by adding the code to instantiate all the pieces for |
|
||
the puzzle. Here's the completed code for i n i t ( ) |
along with all the needed |
|
||
class variables for the puzzle applet: |
|
|
||
public class Puzzle |
extends Applet I |
|
|
|
private Image |
offscreenImage, boardImage; |
|
|
|
private Image[] |
pieceImages = new Image[10]; |
|
|
|
private Graphics |
offscr; |
|
|
|
private Piece[] |
pieces = new Piece[10]; |
|
|
|
private Piece |
picked = null; |
|
|
|
private |
Rectangle |
grid, clickArea; |
|
|
private Font |
bbCourier = new Font( Courier , |
|
|
|
|
|
Font.BOLD, |
48 ) ; |
|
(continued)
(continued)
:rivate String |
winMsg = Win! |
i vate Point |
selectedPiece, winLocation; |
/The pcs[] array specifies the starting position of the
/different puzzle pieces on the 45 grid.
, private int[][] |
pcs = {11,01.10,01,(0,21,(3',01;{3,21, |
|
|
|
(0,41,t1,31,t2,31,13,41,t1,2)1;public void init_:,! |
|
MediaTaacker tracker = new MediaTracker(this); |
|
|
boardImage = getImage(getCodeBase(), board.gif ); |
|
|
tracker.addlmage(boardImage, 0); |
|
|
for (int ii = 0; ii < 10; ii++) |
|
|
|
piece + ii + GIF ); |
|
tracker.addImage(piecelmages[ii], 0); |
|
|
1 |
|
|
try t |
|
i |
tracker.waitForAll(); |
|
|
|
|
v |
I catch (InterruptedException e) I ; } |
|
|
i nt pieceWidth = pieceImages[5].getWidth(null); |
|
|
i nt pieceHeight = piecelmages[51.getHeight(null); |
|
|
int gridX = (boardImage.getWidth(null) - |
|
(pieceWidth * 4)) / 2;
i nt gridY = (boardimage.getHeight(mull) - (pieceHeight * 5)) / 2;
grid = new Rectangle(gridX, gridY,
pieceWidth, pieceHeight); winLocation = new Point(pieceWidth + gridX,
3 * pieceHeight + gridY); clickArea = new Rectangle(gridX, gridY,
4 * pieceWidth,
5 * pieceHeight); for (int ii = O; ii < 10; ii++)
pieces[ii] = new Piece(pcs[ii][0], pcs[ii][1], grid, pieceimages[ii]);
offscreenImage = createlmage(size().width, size().height);
offscr = offscreenImage.getGraphics() repaint();
The puzzle pieces are instantiated inside the last f o r loop. The values in the pcs [ ] [ ] array define the starting position for each piece. Notice also how grid is initialized using the values computed for grid X, grid
and pieceHeight.
