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.

1 ' Fig. 17.24: FrmLogoAnimator.vb 2 ' Program that animates a series of images. 3 Public Class FrmLogoAnimator 4 Private images(29) As Image 5 Private count As Integer = -1 6 7 ' load images 8 Private Sub FrmLogoAnimator_Load(ByVal sender As Object, _ 9 ByVal e As System.EventArgs) Handles Me.Load 10 11 For i As Integer = 0 To images.Length - 1 12 images(i) = Image.FromFile("images\deitel" & i & ".gif") 13 Next 14 15 picLogo.Image = images(0) ' display first image 16 17 ' set PictureBox to be the same size as Image 18 picLogo.Size = picLogo.Image.Size 19 End Sub ' FrmLogoAnimator_Load 20 21 ' display new image every 50 milliseconds 22 Private Sub timer_Tick(ByVal sender As System.Object, _ 23 ByVal e As System.EventArgs) Handles timer.Tick 24 25 count = (count + 1) Mod images.Length ' increment counter 26 picLogo.Image = images(count) ' display next image 27 End Sub ' timer_Tick 28 End Class ' FrmLogoAnimator

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

It is more efficient to load an animation's frames as one image than to load each image separately. (A painting program, such as Adobe Photoshop® or Jasc® Paint Shop Pro™, can be used to combine the animation's frames into one image.) If the images are being loaded separately from the Web, each loaded image requires a separate connection to the site on which the images are stored; this process can result in poor performance.

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.

1 ' Fig. 17.25 : ChessPiece.cs 2 ' Class that represents chess piece attributes. 3 Public Class ChessPiece 4 ' define chess-piece type constants 5 Public Enum Types 6 KING 7 QUEEN 8 BISHOP 9 KNIGHT 10 ROOK 11 PAWN 12 End Enum ' Types 13 14 Private currentType As Integer ' this object's type 15 Private pieceImage As Bitmap ' this object's image 16 17 ' default display location 18 Private targetRectangle As New Rectangle(0, 0, 75, 75) 19 20 ' constructor 21 Public Sub New(ByVal type As Integer, ByVal xLocation As Integer, _ 22 ByVal yLocation As Integer, ByVal sourceImage As Bitmap) 23 24 currentType = type ' set current type 25 targetRectangle.X = xLocation ' set current x location 26 targetRectangle.Y = yLocation ' set current y location 27 28 ' obtain pieceImage from section of sourceImage 29 pieceImage = sourceImage.Clone( _ 30 New Rectangle(type * 75, 0, 75, 75), _ 31 System.Drawing.Imaging.PixelFormat.DontCare) 32 End Sub ' New 33 34 ' draw chess piece 35 Public Sub Draw(ByVal graphicsObject As Graphics) 36 graphicsObject.DrawImage(pieceImage, targetRectangle) 37 End Sub ' end method Draw 38 39 ' obtain this piece's location rectangle 40 Public Function GetBounds() As Rectangle 41 Return targetRectangle 42 End Function ' end method GetBounds 43 44 ' set this piece's location 45 Public Sub SetLocation( _ 46 ByVal xLocation As Integer, ByVal yLocation As Integer 47 48 targetRectangle.X = xLocation 49 targetRectangle.Y = yLocation 50 End Sub ' end method SetLocation 51 End Class ' ChessPiece

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.

1 ' Fig. 17.26: FrmChessGame.vb 2 ' Chess Game graphics code. 3 Public Class FrmChessGame 4 Private chessTile As New ArrayList() ' for tile images 5 Private chessPieces As New ArrayList() ' for chess pieces 6 Private selectedIndex As Integer = -1 ' index for selected piece 7 Private board(,) As Integer = New Integer(7, 7) {} ' board array 8 Private Const TILESIZE As Integer = 75 ' chess tile size in pixels 9 10 ' load tile bitmaps and reset game 11 Private Sub FrmChessGame_Load(ByVal sender As Object, _ 12 ByVal e As System.EventArgs) Handles Me.Load 13 ' load chess board tiles 14 chessTile.Add(Bitmap.FromFile("images\lightTile1.png")) 15 chessTile.Add(Bitmap.FromFile("images\lightTile2.png")) 16 chessTile.Add(Bitmap.FromFile("images\darkTile1.png")) 17 chessTile.Add(Bitmap.FromFile("images\darkTile2.png")) 18 19 ResetBoard() ' initialize board 20 Invalidate() ' refresh form 21 End Sub ' FrmChessGame_Load 22 23 ' initialize pieces to start and rebuild board 24 Private Sub ResetBoard() 25 Dim current As Integer = -1 26 Dim piece As ChessPiece 27 Dim random As New Random() 28 Dim light As Boolean = False 29 Dim type As Integer 30 31 chessPieces.Clear() ' ensure empty arraylist 32 33 ' load whitepieces image 34 Dim whitePieces As Bitmap = _ 35 CType(Image.FromFile("images\whitePieces.png"), Bitmap) 36 37 ' load blackpieces image 38 Dim blackPieces As Bitmap = _ 39 CType(Image.FromFile("images\blackPieces.png"), Bitmap) 40 41 ' set whitepieces to be drawn first 42 Dim selected As Bitmap = whitePieces 43 44 ' traverse board rows in outer loop 45 For row As Integer = 0 To board.GetUpperBound(0) 46 ' if at bottom rows, set to black pieces images 47 If row > 5 Then 48 selected = blackPieces 49 End If 50 51 ' traverse board columns in inner loop 52 For column As Integer = 0 To board.GetUpperBound(1) 53 ' if first or last row, organize pieces 54 If row = 0 OrElse row = 7 Then 55 Select Case column 56 Case 0, 7 ' set current piece to rook 57 current = ChessPiece.Types.ROOK 58 Case 1, 6 ' set current piece to knight 59 current = ChessPiece.Types.KNIGHT 60 Case 2, 5 ' set current piece to bishop 61 current = ChessPiece.Types.BISHOP 62 Case 3 ' set current piece to king 63 current = ChessPiece.Types.KING 64 Case 4 ' set current piece to queen 65 current = ChessPiece.Types.QUEEN 66 End Select 67 68 ' create current piece at start position 69 piece = New ChessPiece(current, _ 70 column * TILESIZE, row * TILESIZE, selected) 71 72 chessPieces.Add(piece) ' add piece to arraylist 73 End If 74 75 ' if second or seventh row, organize pawns 76 If row = 1 OrElse row = 6 Then 77 piece = New ChessPiece(ChessPiece.Types.PAWN, _ 78 column * TILESIZE, row * TILESIZE, selected) 79 chessPieces.Add(piece) ' add piece to arraylist 80 End If 81 82 type = random.Next( 0, 2 ) ' determine board piece type 83 84 If light Then ' set light tile 85 board(row, column) = type 86 light = False 87 Else ' set dark tile 88 board(row, column) = type + 2 89 light = True 90 End If 91 Next column 92 93 light = Not light ' account for new row tile color switch 94 Next row 95 End Sub ' ResetBoard 96 97 ' display board in form OnPaint event 98 Private Sub FrmChessGame_Paint(ByVal sender As Object, _ 99 ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint 100 Dim graphicsObject As Graphics = e.Graphics ' obtain graphics object 101 graphicsObject.TranslateTransform(0, 24) ' adjust origin 102 103 For row As Integer = 0 To board.GetUpperBound(0) 104 For column As Integer = 0 To board.GetUpperBound(1) 105 ' draw image specified in board array 106 graphicsObject.DrawImage( _ 107 CType(chessTile(board(row, column)), Image), _ 108 New Point(TILESIZE * column, (TILESIZE * row))) 109 Next column 110 Next row 111 End Sub ' FrmChessGame_Paint 112 113 ' return index of piece that intersects point 114 ' optionally exclude a value 115 Private Function CheckBounds(ByVal pointValue As Point, _ 116 ByVal exclude As Integer) As Integer 117 Dim rectangleValue As Rectangle ' current bounding rectangle 118 119 For i As Integer = 0 To chessPieces.Count - 1 120 ' get piece rectangle 121 rectangleValue = GetPiece(i).GetBounds() 122 123 ' check if rectangle contains point 124 If rectangleValue.Contains(pointValue) And i <> exclude Then 125 Return i 126 End If 127 Next 128 129 Return -1 130 End Function ' CheckBounds 131 132 ' handle picBoard Paint event 133 Private Sub picBoard_Paint(ByVal sender As Object, _ 134 ByVal e As System.Windows.Forms.PaintEventArgs) _ 135 Handles picBoard.Paint 136 ' draw all pieces 137 For i As Integer = 0 To chessPieces.Count - 1 138 GetPiece(i).Draw(e.Graphics) 139 Next 140 End Sub ' picBoard_Paint 141 142 ' handle picBoard MouseDown event 143 Private Sub picBoard_MouseDown(ByVal sender As Object, _ 144 ByVal e As System.Windows.Forms.MouseEventArgs) _ 145 Handles picBoard.MouseDown 146 ' determine selected piece 147 selectedIndex = CheckBounds(New Point(e.X, e.Y), -1) 148 End Sub ' picBoard_MouseDown 149 150 ' if piece is selected, move it 151 Private Sub picBoard_MouseMove(ByVal sender As Object, _ 152 ByVal e As System.Windows.Forms.MouseEventArgs) _ 153 Handles picBoard.MouseMove 154 155 If selectedIndex > -1 Then 156 Dim region As New Rectangle(e.X - TILESIZE * 2, _ 157 e.Y - TILESIZE * 2, TILESIZE * 4, TILESIZE * 4) 158 159 ' set piece center to mouse 160 GetPiece(selectedIndex).SetLocation( _ 161 e.X - TILESIZE \ 2, e.Y - TILESIZE \ 2) 162 163 picBoard.Invalidate(region) ' refresh region 164 End If 165 End Sub ' picBoard_MouseMove 166 167 ' on mouse up deselect piece and remove taken piece 168 Private Sub picBoard_MouseUp(ByVal sender As Object, _ 169 ByVal e As System.Windows.Forms.MouseEventArgs) _ 170 Handles picBoard.MouseUp 171 172 Dim remove As Integer = -1 173 174 ' if chess piece was selected 175 If selectedIndex > -1 Then 176 Dim current As New Point(e.X, e.Y) 177 Dim newPoint As New Point( _ 178 current.X - (current.X Mod TILESIZE), _ 179 current.Y - (current.Y Mod TILESIZE)) 180 181 ' check bounds with point, exclude selected piece 182 remove = CheckBounds(current, selectedIndex) 183 184 ' snap piece into center of closest square 185 GetPiece(selectedIndex).SetLocation(newPoint.X, newPoint.Y) 186 selectedIndex = -1 ' deselect piece 187 188 ' remove taken piece 189 If remove > -1 Then 190 chessPieces.RemoveAt(remove) 191 End If 192 End If 193 194 picBoard.Invalidate() ' ensure artifact removal 195 End Sub ' picBoard_MouseUp 196 197 ' helper function to convert 198 ' ArrayList object to ChessPiece 199 Private Function GetPiece(ByVal i As Integer ) As ChessPiece 200 Return CType(chessPieces(i), ChessPiece) 201 End Function ' GetPiece 202 203 ' handle NewGame menu option click 204 Private Sub newGameItem_Click(ByVal sender As System.Object, _ 205 ByVal e As System.EventArgs) Handles newGameItem.Click 206 207 ResetBoard() ' reinitialize board 208 Invalidate() ' refresh form 209 End Sub ' newGameItem_Click 210 End Class ' FrmChessGame

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.

Категории