Section 17.12. Animating a Series of Images


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.



Visual BasicR 2005 for Programmers. DeitelR Developer Series
Visual Basic 2005 for Programmers (2nd Edition)
ISBN: 013225140X
EAN: 2147483647
Year: 2004
Pages: 435

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