Storing Brick Information


Managing the Bricks

BricksManager is separated into five broad groups of methods:

  • Loading bricks information.

  • Initializing the bricks data structures.

  • Moving the bricks map.

  • Drawing the bricks.

  • JumperSprite-related tasks. These are mostly various forms of collision detection between the sprite and the bricks.

BricksManager reads a bricks map and creates a Brick object for each brick. The data structure holding the Brick objects is optimized so drawing and collision detection can be carried out quickly.

Moving and drawing the bricks map is analogous to the moving and drawing of an image by a Ribbon object. However, the drawing process is complicated by the ribbon consisting of multiple bricks instead of a single GIF.

Jack, the JumperSprite object, uses BricksManager methods to determine if its planned moves will cause it to collide with a brick.

Loading Bricks Information

BricksManager calls loadBricksFile( ) to load a bricks map; the map is assumed to be in bricksInfo.txt from Images/.

The first line of the file (ignoring comment lines) is the name of the image strip:

     s tiles.gif 5

This means that tiles.gif holds a strip of five images. The map is a series of lines containing numbers and spaces. Each line corresponds to a row of tiles in the game. A number refers to a particular image in the image strip, which becomes a tile. A space means that no tile is used in that position in the game.

The map file may contain empty lines and comment lines (those starting with //), which are ignored.


bricksInfo.txt is:

     // bricks information     s tiles.gif 5     // -----------     44444                                       222222222                                     111                                     2222                                  11111                               444                               444             22222          444                      111          1111112222222     23333  2     33      44444444     00 000111333333000000222222233333  333 2222222223333301     00000000011100000000002220000000003300000111111222222234     // -----------

The images strip in tiles.gif is shown in Figure 12-13.

Figure 12-13. The images strip in tiles.gif


The images strip is loaded with an ImagesLoader object, and an array of BufferedImages is stored in a global variable called brickImages[].

This approach has several drawbacks. One is the reliance on single digits to index into the images strip. This makes it impossible to utilize strips with more than 10 images (images can only be named from 0 to 9), which is inadequate for a real map. The solution probably entails moving to a letter-based scheme (using A-Z and/or a-z) to allow up to 52 tiles.

loadBricksFile( ) calls storeBricks( ) to read in a single map line, adding Brick objects to a bricksList ArrayList:

     private void storeBricks(String line, int lineNo, int numImages)     {       int imageID;       for(int x=0; x < line.length( ); x++) {         char ch = line.charAt(x);         if (ch == ' ')   // ignore a space           continue;         if (Character.isDigit(ch)) {           imageID = ch - '0';    // Assume a digit is 0-9           if (imageID >= numImages)             System.out.println("Image ID "+imageID+" out of range");           else   // make a Brick object                  bricksList.add( new Brick(imageID, x, lineNo) );         }         else           System.out.println("Brick char " + ch + " is not a digit");       }     }

A Brick object is initialized with its image ID (a number in the range 0 to 9); a reference to the actual image is added later. The brick is passed its map indices (x, lineNo). lineNo starts at 0 when the first map line is read and is incremented with each new line.

Figure 12-14 shows some of the important variables associated with a map, including example map indices.

Initializing the Bricks Data Structures

Once the bricksList ArrayList has been filled, BricksManager calls initBricksInfo( ) to extract various global data from the list and to check if certain criteria are met. For instance, the maximum width of the map should be greater than the width of the

Figure 12-14. Brick map variables


panel (width pWidth). initBricksInfo( ) calls checkForGaps( ) to check that no gaps are in the maps bottom row. The presence of a gap would allow Jack to fall down a hole while running around, which would necessitate more complex coding in JumperSprite. If checkForGaps( ) finds a gap, the game terminates after reporting the error. The bricksList ArrayList doesn't store its Brick objects in order, which makes finding a particular Brick time-consuming. Unfortunately, searching for a brick is a common task and must be performed every time that Jack is about to move to prevent it from hitting something.

A more useful way of storing the bricks map is ordered by column, as illustrated in Figure 12-15.

Figure 12-15. Bricks stored by column


This data structure is excellent for brick searches where the column of interest is known beforehand since the array allows constant-time access to a given column.

A column is implemented as an ArrayList of Bricks in no particular order, so a linear search looks for a brick in the selected column. However, a column contains few bricks compared to the entire map, so the search time is acceptable. Since no gaps are in the bottom row of the map, each column must contain at least one brick, guaranteeing that none of the column ArrayLists in columnBricks[] is null.

The columnBricks[] array is built by BricksManager calling createColumns( ).

Moving the Bricks Map

The BricksManager uses the same approach to moving its bricks map as the Ribbon class does for its GIF.

The isMovingRight and isMovingLeft flags determine the direction of movement for the bricks map (or if it is stationary) when its JPanel position is updated. The flags are set by the moveRight( ), moveLeft( ), and stayStill( ) methods:

     public void moveRight( )     { isMovingRight = true;       isMovingLeft = false;     }

update( ) increments an xMapHead value depending on the movement flags. xMapHead is the x-coordinate in the panel where the left edge of the bricks map (its head) should be drawn. xMapHead can range between -width to width (where width is the width of the bricks map in pixels):

     public void update( )     { if (isMovingRight)         xMapHead = (xMapHead + moveSize) % width;       else if (isMovingLeft)         xMapHead = (xMapHead - moveSize) % width;     }

Drawing the Bricks

The display( ) method does the hard work of deciding where the bricks in the map should be drawn in the JPanel.

As in the Ribbon class, several different coordinate systems are combined: the JPanel coordinates and the bricks map coordinates. The bad news is that the bricks map uses two different schemes. One way of locating a brick is by its pixel position in the bricks map; the other is by using its map indices (see Figure 12-14). This means that three coordinate systems are utilized in display( ) and its helper method drawBricks( ):

     public void display(Graphics g)     {       int bCoord = (int)(xMapHead/imWidth) * imWidth;       // bCoord is the drawing x-coord of the brick containing xMapHead       int offset;    // offset is distance between bCoord and xMapHead       if (bCoord >= 0)         offset = xMapHead - bCoord;   // offset is positive       else  // negative position         offset = bCoord - xMapHead;   // offset is positive       if ((bCoord >= 0) && (bCoord < pWidth)) {         drawBricks(g, 0-(imWidth-offset), xMapHead,                                 width-bCoord-imWidth);   // bm tail         drawBricks(g, xMapHead, pWidth, 0);  // bm start       }       else if (bCoord >= pWidth)         drawBricks(g, 0-(imWidth-offset), pWidth,                                  width-bCoord-imWidth);  // bm tail       else if ((bCoord < 0) && (bCoord >= pWidth-width+imWidth))         drawBricks(g, 0-offset, pWidth, -bCoord);        // bm tail       else if (bCoord < pWidth-width+imWidth) {         drawBricks(g, 0-offset, width+xMapHead, -bCoord);  // bm tail         drawBricks(g, width+xMapHead, pWidth, 0);     // bm start       }     } // end of display( )

The details of drawBricks( ) will be explained later in the chapter. For now, it's enough to know the meaning of its prototype:

     void drawBricks(Graphics g, int xStart, int xEnd, int xBrick);

drawBricks( ) draws bricks into the JPanel starting at xStart, ending at xEnd. The bricks are drawn a column at a time. The first column of bricks is the one at the xBrick pixel x-coordinate in the bricks map.

display( ) starts by calculating a brick coordinate (bCoord) and offset from the xMapHead position. These are used in the calls to drawBricks( ) to specify where a brick image's left edge should appear. This should become clearer as you consider the four drawing cases.

Case 1. Bricks map moving right and bCoord is less than pWidth

This is the relevant code snippet in display( ):

     if ((bCoord >= 0) && (bCoord < pWidth)) {       drawBricks(g, 0-(imWidth-offset), xMapHead,                             width-bCoord-imWidth);   // bm tail       drawBricks(g, xMapHead, pWidth, 0);  // bm start     }  // bm means bricks map

Figure 12-16 illustrates the drawing operations:

Case 1 occurs as the bricks map moves right since the sprite is apparently moving left. xMapHead will have a value between 0 and pWidth (the width of the JPanel). Two groups of bricks will need to be drawn, requiring two calls to drawBricks( ). The first group starts near the left edge of the JPanel, and the second starts at the xMapHead position. I've indicated these groups by drawing the bricks map area occupied by the left group in gray in Figure 12-16 and the righthand group's area with stripes.

The positioning of the bricks in the gray area of the bricks map in Figure 12-16 poses a problem. The drawing of a column of bricks requires the x-coordinate of the column's

Figure 12-16. Case 1 in BricksManager's display( )


left edge. What is that coordinate for the first column drawn in the gray area of the bricks map?

The left edge of that column will usually not line up with the left edge of the panel, most likely occurring somewhere to its left and off screen. The required calculation (width-bCoord-imWidth) is shown in Figure 12-16, next to the leftmost arrow at the bottom of the figure.

The drawing of a group of bricks is packaged up in drawBricks( ). The second and third arguments of that method are the start and end x-coordinates for a group in the JPanel. These are represented by arrows pointing to the JPanel box at the top of Figure 12-16. The fourth argument is the x-coordinate of the left column of the group in the bricks map. These coordinates are represented by the arrows at the bottom of Figure 12-16.

drawBricks( ) is called twice in the code snippet shown earlier: once for the group in the lefthand gray area of the bricks map in Figure 12-16, and once for the group in the righthand striped area.

Case 2. Bricks map moving right and bCoord is greater than pWidth

Here's the code piece:

     if (bCoord >= pWidth)       drawBricks(g, 0-(imWidth-offset), pWidth,                               width-bCoord-imWidth);  // bm tail

Figure 12-17 shows the operation.

Figure 12-17. Case 2 in BricksManager's display( )


Case 2 happens some time after Case 1, when xMapHead has moved farther right, beyond the right edge of the JPanel. The drawing task becomes simpler since only a single call to drawBricks( ) is required to draw a group of columns taken from the middle of the bricks map. I've indicated that group's area in gray in the bricks map in Figure 12-17.

Case 2 has the same problem as Case 1 in determining the x-coordinate of the left column of the gray group in the bricks map. The value is shown next to the leftmost bottom arrow in Figure 12-17.

Case 3. Bricks map moving left and bCoord is greater than (pWidth-width+imWidth)

The relevant code fragment is shown here:

     if ((bCoord < 0) && (bCoord >= pWidth-width+imWidth))       drawBricks(g, 0-offset, pWidth, -bCoord);             // bm tail

Figure 12-18 illustrates the drawing operation.

Case 3 applies when the bricks map is moving left, as the sprite is apparently traveling to the right. xMapHead goes negative, as does bCoord, but the calculated offset is adjusted to be positive.

Until bCoord drops below (pWidth-width+imWidth), the bricks map will only require one drawBricks( ) call to fill the JPanel.

Figure 12-18. Case 3 in BricksManager's display( )


Case 4. Bricks map moving left and bCoord is less than (pWidth-width+ imWidth)

Here's the code:

     if (bCoord < pWidth-width+imWidth) {       drawBricks(g, 0-offset, width+xMapHead, -bCoord);  // bm tail       drawBricks(g, width+xMapHead, pWidth, 0);     // bm start     }

Figure 12-19 shows the operations.

Case 4 occurs after xMapHead has moved to the left of (pWidth-width+imWidth). Two drawBricks( ) calls are needed to render two groups of columns to the JPanel. The group's areas are shown in solid gray and striped in the bricks map in Figure 12-19.

The drawBricks( ) method

drawBricks( ) draws bricks into the JPanel between xStart and xEnd. The bricks are drawn a column at a time, separated by imWidth pixels. The first column of bricks drawn is the one at the xBrick pixel x-coordinate in the bricks map:

     private void drawBricks(Graphics g, int xStart, int xEnd, int xBrick)     { int xMap = xBrick/imWidth;   // get column position of the brick                                    // in the bricks map       ArrayList column;       Brick b;       for (int x = xStart; x < xEnd; x += imWidth) {         column = columnBricks[ xMap ];   // get the current column         for (int i=0; i < column.size( ); i++) {   // draw all bricks

Figure 12-19. Case 4 in BricksManager's display( )


            b = (Brick) column.get(i);            b.display(g, x);   // draw brick b at JPanel posn x         }         xMap++;  // examine the next column of bricks       }     }

drawBricks( ) converts the xBrick value, a pixel x-coordinate in the bricks map, into a map x index. This index is the column position of the brick, so the entire column can be accessed immediately in columnBricks[]. The bricks in the column are drawn by calling the display( ) method for each brick.

Only the JPanel's x-coordinate is passed to display( ) with the y-coordinate stored in the Brick object. This is possible since a brick's y-axis position never changes as the bricks map is moved horizontally over the JPanel.

JumperSprite-Related Methods

The BricksManager has several public methods used by JumperSprite to determine or check its position in the bricks map. The prototypes of these methods are:

     int findFloor(int xSprite);     boolean insideBrick(int xWorld, int yWorld);     int checkBrickBase(int xWorld, int yWorld, int step);     int checkBrickTop(int xWorld, int yWorld, int step);

Finding the floor

When Jack is added to the scene, his x-coordinate is in the middle of the JPanel, but what should his y-coordinate be? His feet should be placed on the top-most brick at or near the given x-coordinate. findFloor( ) searches for this brick, returning its y-coordinate:

     public int findFloor(int xSprite)     {       int xMap = (int)(xSprite/imWidth);   // x map index       int locY = pHeight;    // starting y pos (largest possible)       ArrayList column = columnBricks[ xMap ];       Brick b;       for (int i=0; i < column.size( ); i++) {         b = (Brick) column.get(i);         if (b.getLocY( ) < locY)           locY = b.getLocY( );   // reduce locY (i.e., move up)       }       return locY;     }

Matters are simplified by the timing of the call: findFloor( ) is invoked before the sprite has moved and, therefore, before the bricks map has moved. Consequently, the sprite's x-coordinate in the JPanel (xSprite) is the same x-coordinate in the bricks map.

xSprite is converted to a map x index to permit the relevant column of bricks to be accessed in columnBricks[].

Testing for brick collision

JumperSprite implements collision detection by calculating its new position after a proposed move and by testing if that point (xWorld, yWorld) is inside a brick. If it is, then the move is aborted and the sprite stops moving.

The point testing is done by BricksManager's insideBrick( ), which uses worldToMap( ) to convert the sprite's coordinate to a brick map index tuple:

     public boolean insideBrick(int xWorld, int yWorld)     // Check if the world coord is inside a brick     {       Point mapCoord = worldToMap(xWorld, yWorld);       ArrayList column = columnBricks[ mapCoord.x ];       Brick b;       for (int i=0; i < column.size( ); i++) {         b = (Brick) column.get(i);         if (mapCoord.y == b.getMapY( ))           return true;       }       return false;     }  // end of insideBrick( )

worldToMap( ) returns a Point object holding the x and y map indices corresponding to (xWorld, yWorld). The relevant brick column in columnBricks[] can then be searched for a brick at the y map position.

The conversion carried out by worldToMap( ) can be understood by referring to Figure 12-14. Here's the code:

     private Point worldToMap(int xWorld, int yWorld)     // convert world coord (x,y) to a map index tuple     {       xWorld = xWorld % width;   // limit to range (width to -width)       if (xWorld < 0)            // make positive         xWorld += width;       int mapX = (int) (xWorld/imWidth);   // map x-index       yWorld = yWorld - (pHeight-height);  // relative to map       int mapY = (int) (yWorld/imHeight);  // map y-index       if (yWorld < 0)   // above the top of the bricks         mapY = mapY-1;  // match to next 'row' up       return new Point(mapX, mapY);     }

xWorld can be any positive or negative value, so it must be restricted to the range (0 to width), which is the extent of the bricks map. The coordinate is then converted to a map a index.

The yWorld value uses the JPanel's coordinate system, so it is made relative to the y-origin of the bricks map (some distance down from the top of the JPanel). The conversion to a map y index must take into account the possibility that the sprite's position is above the top of the bricks map. This can occur by having the sprite jump upward while standing on a platform at the top of the bricks map.

Jumping and hitting your head

When Jack jumps, his progress upward will be halted if he is about to pass through the base of a brick. The concept is illustrated in Figure 12-20.

The sprite hopes to move upward by a step amount, but this will cause it to enter the brick. Instead, it will travel upward by a smaller step, step-(imHeight-topOffset), placing its top edge next to the bottom edge of the brick.

checkBrickBase( ) is supplied with the planned new position (xWorld, yWorld)labeled as (x, y) in Figure 12-20and the step. It returns the step distance that the sprite can move without passing into a brick:

     public int checkBrickBase(int xWorld, int yWorld, int step)     {       if (insideBrick(xWorld, yWorld)) {         int yMapWorld = yWorld - (pHeight-height);         int mapY = (int) (yMapWorld/imHeight);  // map y- index         int topOffset = yMapWorld - (mapY * imHeight);         return (step - (imHeight-topOffset));  // a smaller step       }       return step;   // no change     }

Figure 12-20. A rising sprite hitting a brick


Falling and sinking into the ground

As a sprite descends, during a jump or after walking off the edge of a raised platform, it must test its next position to ensure that it doesn't pass through a brick on its way down. When a brick is detected beneath the sprite's feet, the descent is stopped, ensuring that the Jack lands on top of the brick. Figure 12-21 illustrates the calculation.

The sprite moves downward by a step amount on each update, but when a collision is detected, the step size is reduced to step-topOffset so it comes to rest on top of the brick:

     public int checkBrickTop(int xWorld, int yWorld, int step)     {       if (insideBrick(xWorld, yWorld)) {         int yMapWorld = yWorld - (pHeight-height);         int mapY = (int) (yMapWorld/imHeight);  // map y- index         int topOffset = yMapWorld - (mapY * imHeight);         return (step - topOffset);    // a smaller step       }       return step;   // no change     }

The intended new position for the sprite (xWorld, yWorld) is passed to checkBrickTop( ), along with the step size. The returned value is the step the sprite should take to avoid sinking into a brick.

Figure 12-21. A falling sprite hitting a brick




Killer Game Programming in Java
Killer Game Programming in Java
ISBN: 0596007302
EAN: 2147483647
Year: 2006
Pages: 340

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net