Visual Basic 2005 for Programmers (2nd Edition)
17.12. Animating a Series of Images
The next example animates a series of images stored in an array. The application uses the same technique to load and display Images as shown in Fig. 17.23. The animation in Fig. 17.24 uses a PictureBox, which contains the images that we animate. We use a Timer to cycle through the images and display a new image every 50 milliseconds. Variable count keeps track of the current image number and increases by 1 every time we display a new image. The array includes 30 images (numbered 029); when the application reaches image 29, it returns to image 0. The 30 images are located in the images folder inside the project's bin/Debug and bin/Release directories. Figure 17.24. Animation of a series of images.
Lines 1113 load each of 30 images and place them in an array of Images (declared in line 4). Line 15 places the first image in the PictureBox. Line 18 modifies the size of the PictureBox so that it is equal to the size of the Image it is displaying. The event handler for timer's Tick event (line 2227) responds to each event by displaying the next image from the array. Performance Tip 17.2
Chess Example
The following chess example demonstrates techniques for two-dimensional collision detection, selecting single frames from a multiframe image, and regional invalidation, refreshing only the parts of the screen that have changed, to increase performance. Twodimensional collision detection enables a program to detect whether two shapes overlap or whether a point is contained within a shape. In the next example, we demonstrate the simplest form of collision detection, which determines whether a point (the mouse-click location) is contained within a rectangle (a chess-piece image). Class ChessPiece (Fig. 17.25) represents the individual chess pieces. Lines 512 define a Public enumeration of constants that identify each chess-piece type. The constants also serve to identify the location of each piece in the chess-piece image file. Rectangle object targetRectangle (line 18) identifies the image location on the chessboard. The x and y properties of the rectangle are assigned in the ChessPiece constructor, and all chess-piece images have a width and height of 75 pixels. Figure 17.25. Class that represents chess-piece attributes.
The ChessPiece constructor (lines 2132) receives the chess-piece type, its x and y location and the Bitmap containing all chess-piece images. Rather than loading the chesspiece image within the class, we allow the calling class to pass the image. This increases the flexibility of the class by allowing the user to change images. Lines 2931 extract a subimage that contains only the current piece's bitmap data. Our chess-piece images are defined in a specific manner: One image contains six chess-piece images, each defined within a 75-pixel block, resulting in a total image size of 450-by-75. We obtain a single image via Bitmap's Clone method, which allows us to specify a rectangle-image location and the desired pixel format. The location is a 75-by-75 pixel block with its upper-left corner x equal to 75 * type, and the corresponding y equal to 0. For the pixel format, we specify constant DontCare, causing the format to remain unchanged. Method Draw (lines 3537) causes the ChessPiece to draw pieceImage in the targetRectangle using the Graphics object passed as Draw's argument. Method GetBounds (lines 4052) returns the targetRectangle object for use in collision detection, and method SetLocation (lines 4550) allows the calling class to specify a new piece location. Class FrmChessGame (Fig. 17.26) defines the game and graphics code for our chess game. Lines 48 define instance variables the program requires. ArrayList chessTile (line 4) stores the board tile images. ArrayList chessPieces (line 5) stores all active ChessPiece objects, and IntegerselectedIndex (line 6) identifies the index in chess-Pieces of the currently selected piece. The board (line 7) is an 8-by-8, two-dimensional Integer array corresponding to the squares of a chess board. Each board element is an integer from 0 to 3 that corresponds to an index in chessTile and is used to specify the chessboard-square image. Const TILESIZE (line 8) defines the size of each tile in pixels. Figure 17.26. Chess-game code.
The chess game GUI consists of Form ChessGame, the area in which we draw the tiles; Panel pieceBox, the area in which we draw the pieces (note that pieceBox's background color is set to "transparent"); and a Menu that allows the user to begin a new game. Although the pieces and tiles could have been drawn on the same form, doing so would decrease performance. We would be forced to refresh the board and all the pieces every time we refreshed the control. The ChessGame_Load event handler (lines 1121) loads four tile images into chessTiletwo light tiles and two dark tiles for variety. It then calls method ResetBoard to refresh the Form and begin the game. Method ResetBoard (lines 2495) clears chess-Pieces, loads images for both the black and the white chess-piece sets and creates Bitmap selected to define the currently selected Bitmap set. Lines 4594 loop through the board's 64 positions, setting the tile color and piece for each tile. Lines 4749 cause the currently selected image to switch to the blackPieces after the fifth row. If the row counter is on the first or last row, lines 5566 add a new piece to chessPieces. The type of the piece is based on the current column we are initializing. Pieces in chess are positioned in the following order, from left to right: rook, knight, bishop, queen, king, bishop, knight and rook. Lines 7680 add a new pawn at the current location if the current row is second or seventh. A chessboard is defined by alternating light and dark tiles across a row in a pattern where the color that starts each row is equal to the color of the last tile of the previous row. Lines 8290 assign the current board-tile color to an element in the board array. Based on the alternating value of Boolean variable light and the results of the random operation in line 82, we assign an Integer to the board to determine the color of that tile0 and 1 represent light tiles; 2 and 3 represent dark tiles. Line 93 inverts the value of light at the end of each row to maintain the staggered effect of a chessboard. Method FrmChessGame_Paint (lines 98111) handles the Form's Paint event and draws the tiles according to their values in the board array. Since the default height of a MenuStrip is 24 pixels, we use the translateTransform method of class Graphics to shift the origin of the Form down 24 pixels (line 101). This shift prevents the top row of tiles from being hidden behind the MenuStrip. Method picBoard_Paint (lines 133140), which handles the Paint event for the picBoard Panel, iterates through each element of the chessPieces ArrayList and calls its Draw method. The picBoard MouseDown event handler (lines 143148) calls CheckBounds (declared in lines 115130) with the location of the mouse to determine whether the user has selected a piece. The picBoard MouseMove event handler (lines 151165) moves the selected piece with the mouse. Lines 156157 define a region of the Panel that spans two tiles in every direction from the pointer. As mentioned previously, Invalidate is slow. This means that the picBoard MouseMove event handler might be called several times before the Invalidate method completes. If a user working on a slow computer moves the mouse quickly, the application could leave behind artifacts. An artifact is any unintended visual abnormality in a graphical program. By causing the program to refresh a two-square rectangle, which should suffice in most cases, we achieve a significant performance enhancement over an entire component refresh during each MouseMove event. Lines 160161 set the selected piece location to the mouse-cursor position, adjusting the location to center the image on the mouse. Line 163 invalidates the region defined in lines 156157 so that it will be refreshed. Lines 168195 define the picBoard MouseUp event handler. If a piece has been selected, lines 175192 determine the index in chessPieces of any piece collision, remove the collided piece, snap (align) the current piece to a valid location and deselect the piece. We check for piece collisions to allow the chess piece to "take" other chess pieces. Line 182 checks whether any piece (excluding the currently selected piece) is beneath the current mouse location. If a collision is detected, the returned piece index is assigned to remove. Line 185 determine the closest valid chess tile and "snaps" the selected piece to that location. If remove contains a positive value, line 190 removes the object at that index from the chessPieces ArrayList. Finally, the entire Panel is invalidated in line 194 to display the new piece location and remove any artifacts created during the move. Method CheckBounds (lines 115130) is a collision-detection helper method; it iterates through ArrayList chessPieces and returns the index of any piece's rectangle that contains the point passed to the method (the mouse location, in this example). CheckBounds uses Rectangle method Contains to determine whether a point is in the Rectangle. Method CheckBounds optionally can exclude a single piece index (to ignore the selected index in the picBoard MouseUp event handler, in this example). Lines 199201 define helper function GetPiece, which simplifies the conversion from objects in ArrayList chessPieces to ChessPiece types. Event handler newGameItem_Click (lines 204209) handles the NewGame menu-item click event, calls RefreshBoard to reset the game and invalidates the entire form. |