Animation in VB (Part 1)

At some point in time, any programmer has the idea to write a game. Some people become programmers because they want to write their own games. In this series of articles, you will look at how to animate the game using some common techniques. In this first article, you will look at using stick figures and simple 2D vector graphics.

Stick Figures

We've all done stick figure animation at some time, especially when bored in class or work. You draw a simple animation on the corners of the pages in a book and flick through the pages, producing the same little animation over and over. This type of animation is generally static, where each frame of the animation is always the same when played back.

When it comes to creating this animation in an application, there are several things that need to be considered: Speed, Total Frames, Size of frames, and method of display.

In the first example, you are going to do a simple stick man animation. Looking at the list, you are going to use 40ms between frames (25 FPS), 24 frames, 440 by 230 pixel frame, and you are going to redraw each frame in real time.

Before starting with the code there is something to note: This is not the only way to do this style of animation, but only a guide on how to get started. In this animation, you are going to use the Line and Circle methods to draw a few simple shapes that, when put all together, provide the wanted animation.

In drawing the frames, you will use a weird method that works a little easier than some others. In your picturebox, you use the Invert pen for drawing each line and circle. This way, you simply can redraw a line in the next frame to clear it from the image.

The first thing you are going to do is set up the base frame. In this animation most of the frames will have the identical base image, and you only alter a few lines and circles at a time. You paint this frame when the form loads so that at startup the base image is displayed.

Private Sub Form_Load()
   M.DrawMode = 6    'Invert
   M.Line (300, 120)-(300, 170)
   M.Line (280, 130)-(320, 130)
   M.Line (300, 170)-(280, 190)
   M.Line (300, 170)-(320, 190)
   M.Circle (300, 105), 15
   M.Line (100, 120)-(100, 170)
   M.Line (80, 130)-(120, 130)
   M.Line (100, 170)-(80, 190)
   M.Line (100, 170)-(120, 190)
   M.Circle (100, 105), 15
   M.Line (120, 130)-(200, 190)
End Sub

When you start the application, the base image will look like this.

As you can see, by using a few lines and circles you can draw two simple stick figure men.

I've found it easier to use a Timer class to provide the necessary timing; more about using a timer can be found in this article. And, most of the animation takes place within the timers' Tick event. Apart from the timer, you use a variable to keep track of which frame you are currently displaying.

When you want to start the animation, you set the tracking variable to the first frame, and then start the timer.

Private Sub Command1_Click()
   Istep = 1
   Timer1.Interval = 40
   Timer1.Enabled = True
End Sub

In the Timer's Tick event, you now handle all the changes that occur between frames.

Private Sub Timer1_Timer()
   M.DrawMode = 6
Select Case Istep
   Case 1
      M.Line (120, 130)-(200, 190)

      M.Line (120, 130)-(180, 190)
   Case 2
      M.Line (100, 130)-(120, 130)
      M.Line (120, 130)-(180, 190)

      M.Line (100, 130)-(118, 120)
      M.Line (118, 120)-(175, 190)
   Case 3
      M.Line (100, 130)-(118, 120)
      M.Line (118, 120)-(175, 190)

      M.Line (100, 130)-(115, 110)
      M.Line (115, 110)-(165, 190)
   Case 4
      M.Line (100, 130)-(115, 110)
      M.Line (115, 110)-(165, 190)

      M.Line (100, 130)-(105, 110)
      M.Line (105, 110)-(155, 180)
   Case 5
      M.Line (100, 130)-(105, 110)
      M.Line (105, 110)-(155, 180)

      M.Line (100, 130)-(95, 110)
      M.Line (95, 110)-(145, 170)
'...... More frames
End Select

If Istep > 0 Then Istep = Istep + 1
End Sub

In this snip, you can see that only a few of the drawn lines change between frames. You first erase the lines that are not used from the previous frame and then draw in the lines for the current frame. In this way, there is no need to clear the image and redraw the entire frame. Something to remember is that, if the time between clearing and drawing the last lines/pixels of an image is too long, you will end up with a flicker in the animation.

Download the 'StickFigure' sample at the end of this page to view the rest of the animation, and to see a full working example of this animation method.

On the next page, you will look at simple vector graphics.

Animation in VB (Part 1)

Having done some simple stick figure animation and seeing how easy it is to do static frames, you are going to add to this base. Using simple 2D vectors, you are going to animate some non-static frames, accept input from the user, and detect collisions. In doing all this, you will create your first fully animated game.

While thinking about how to best present simple 2D vector graphics, I saw an Asteroids Clone game, and decided this would be one of the easiest games to use as a base for the 2D Vectors section of the article.

When starting with this style of game, you need to first create our vector graphics. You need one for the ship, several for the Asteroids that float around, and for the shots (or rockets) fired.

Before starting with the final objects, you need to make a UDT to hold the coordinates for each point of our vectors.

Public Type Cords_Type
   X As Single
   Y As Single
End Type

Because you are working with 2D vectors, there is no need to include a Z-axis into the type. Also, using a Single typed variable will improve the smoothness and accuracy of the animation.

When building the ship variables, you include them in a UDT that will make it a little simpler to reference all variables required for it.

Public Type Ship_Type
   Speed As Single
   Direction As Single
   Angle As Single
   Location As Cords_Type
   Cords(3) As Cords_Type
   Ang_Const(2) As Single
   Len_Const(2) As Single
End Type

Public Ship As Ship_Type

Most variables are self explanatory. However, here is a full description.

  • Speed: Angular velocity in pixels per frame
  • Direction: Direction of Motion in Degrees
  • Angle: Direction that ship is pointing
  • Location: Actual location of the center of the ship on the screen
  • Cords: Coordinates of the center and Vector points of the ship
  • Ang_Const: Calculated angle to each vector point from the center
  • Len_Const: Calculated Angular length to each vector point from the center

Once defined, your ship's variables need their defaults to be set. These include placing the ship in the center of the playing field, and then setting its three vector points and center.

With Ship
   .Speed      = 0
   .Angle      = 180
   .Direction  = 0
   .Cords(0).X = 7
   .Cords(0).Y = 10
   .Cords(1).X = 7
   .Cords(1).Y = 0
   .Cords(2).X = 0
   .Cords(2).Y = 20
   .Cords(3).X = 14
   .Cords(3).Y = 20
   .Location.X = 320
   .Location.Y = 240
End With

After passing this through the rendering code, the ship will appear like this.

[Ship.jpg]

Your asteroids will be a very similar UDT.

Public Type Blocks_Type
   Speed As Single
   Cords(10) As Cords_Type
   Ang_Const(9) As Single
   Len_Const(9) As Single
   Size As Byte
   Location As Cords_Type
   First As Boolean
   Vis As Boolean
   Direction As Single
   Rotate As Single
   Angle As Single
End Type

Public Asteroids(39) As Blocks_Type

The Description of each variable is as follows.

  • Speed: Angular Velocity
  • Cords: Coordinates of the center and Vector points of the Asteroid
  • Ang_Const: Calculated angle to each vector point from the center
  • Len_Const: Calculated Angular length to each vector point from the center
  • Size: The size Divisor of this asteroid (larger number is smaller displayed asteroid)
  • Location: Actual location of center of Asteroid on the screen
  • First: Used for the rendering
  • Vis: Used for the rendering
  • Direction: Direction of Motion in Degrees
  • Rotate: Rotational Velocity of asteroid
  • Angle: Rotation angle of the asteroid

Again, some of the asteroids variables need their defaults to be set.

With Def_Ast
   .Speed       = 0.5
   .Size        = 1
   .Cords(0).X  = 50
   .Cords(0).Y  = 50
   .Cords(1).X  = 50
   .Cords(1).Y  = 0
   .Cords(2).X  = 70
   .Cords(2).Y  = 5
   .Cords(3).X  = 65
   .Cords(3).Y  = 10
   .Cords(4).X  = 100
   .Cords(4).Y  = 45
   .Cords(5).X  = 95
   .Cords(5).Y  = 80
   .Cords(6).X  = 60
   .Cords(6).Y  = 100
   .Cords(7).X  = 25
   .Cords(7).Y  = 75
   .Cords(8).X  = 0
   .Cords(8).Y  = 70
   .Cords(9).X  = 5
   .Cords(9).Y  = 30
   .Cords(10).X = 25
   .Cords(10).Y = 10
End With

After rendering, the asteroids will look something like this.

[Blocks.jpg]

Your Shots UDT is simple and does not require defaults to be set.

Public Type Shot_Type
   Speed As Single
   Direction As Single
   Location As Cords_Type
   TTL As Single
   First As Boolean
End Type

Public Shots(19) As Shot_Type

The Description of each variable is as follows.

  • Speed: Angular Velocity
  • Location: Actual location of the shot on the screen
  • Direction: Direction of Motion in Degrees
  • TTL: Used for the rendering
  • First: Used for the rendering

After you've set up your variables, you can start with the game play. First, you need to check whether the user has pressed any buttons to accelerate or turn the ship. For this, you set the form's Key preview to true, so that you have a single event handling all key presses.

Public Enum Tri_Stat
   neg = -1
   Zero = 0
   pos = 1
End Enum

Public Turn As Tri_Stat
Public Accelerate As Boolean
Public Warp As Boolean
Public Shoot As Boolean

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
   Case 39
      Turn = pos
   Case 37
      Turn = neg
   Case 38
      Accelerate = True
   Case 40
      Warp = True
   Case 32
      Shoot = True
End Select
'32 = Space
'38 = Up
'40 = Down
'39 = right
'37 = left
End Sub

Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
   Case 39, 37
      Turn = Zero
   Case 38
      Accelerate = False
End Select
End Sub

That takes care of all the input that your games requires. Game play is with the arrow keys and space bar.

On the next page, you look at the rendering core for each of your objects.

Animation in VB (Part 1)

Before rendering the objects, you look at the angular and length constants for each object. You need to calculate these after the coordinates for your objects have been set; however, you only need to calculate them once.

Public Sub Init_const()
With Ship
   For Loop_1 = 0 To 2
      .Ang_Const(Loop_1) = Atn((.Cords(Loop_1 + 1).X - _
         .Cords(0).X) / (.Cords(Loop_1 + 1).Y - .Cords(0).Y))
      .Len_Const(Loop_1) = (.Cords(0).Y - _
         .Cords(Loop_1 + 1).Y) / Cos(.Ang_Const(Loop_1))
   Next Loop_1
End With

With Def_Ast
   For Loop_1 = 0 To 9
      .Ang_Const(Loop_1) = Atn((.Cords(Loop_1 + 1).X - _
         .Cords(0).X) / (.Cords(Loop_1 + 1).Y - .Cords(0).Y))
      .Len_Const(Loop_1) = (.Cords(0).Y - _
         .Cords(Loop_1 + 1).Y) / Cos(.Ang_Const(Loop_1))
   Next Loop_1
End With
Score = 0
End Sub

In this sub, you can see that you use some simple trigonometric mathematics: mostly Sin, Cos, and ArcTan. These three mathematical functions are used extensively when rendering the vectors. In simple terms, all this function does is find the angle to each vector point from center and the distance. With just these two values, you now can calculate the new X,Y location of each point as the angle changes or the center moves.

You now look at rendering the vectors for each of our objects. You again use a timer to keep the game play in real time. You set the timer's interval to 15ms; this gives you about 65 frames per second.

Private Sub Timer1_Timer()
If Not Dead Then
   Check_Input
   Put_Ship
Else
   Check_Space
End If
Put_Shots
Put_Aster
Hit_Aster
Check_Stage
Do_Score
End Sub

In the timer's Tick event, you first process all the user inputs that affect the ship and shots fired. You then render the ship, shots, and asteroids. Then, you check for collisions, how many asteroids are left, and finally show the score.

Private Sub Check_Input()
   Select Case Turn
      Case pos
         Ship.Angle = Ship.Angle + 1.5
         If Ship.Angle >= 360 Then Ship.Angle = Ship.Angle - 360
      Case neg
         Ship.Angle = Ship.Angle - 1.5
         If Ship.Angle < 0 Then Ship.Angle = Ship.Angle + 360
   End Select
   If Accelerate Then
      With Ship
         Tmp.X = .Speed * Sin(.Direction * PI / 180)
         Tmp.Y = .Speed * Cos(.Direction * PI / 180)
         Tmp.X = Tmp.X + (0.02 * Sin(.Angle * PI / 180))
         Tmp.Y = Tmp.Y + (0.02 * Cos(.Angle * PI / 180))
         If Tmp.Y = 0 Then
            .Direction = (Atn(Tmp.Y / Tmp.X)) / PI * 180
            .Speed = Tmp.X / (Cos(.Direction * PI / 180))
         Else
            .Direction = (Atn(Tmp.X / Tmp.Y)) / PI * 180
            .Speed = Tmp.Y / (Cos(.Direction * PI / 180))
         End If
         If .Speed > 2  Then .Speed = 2
         If .Speed < -2 Then .Speed = -2
      End With
   End If
   If Warp Then
      With Ship
         .Location.X = Rnd() * 640
         .Location.Y = Rnd() * 480
         .Angle = Rnd() * 360
         .Direction = Rnd() * 360
      End With
      Warp = False
   End If
   If Shoot Then
      For Loop_1 = 0 To 19
         If Shots(Loop_1).TTL = 0 Then
            Shots(Loop_1).Direction = Ship.Angle
            Shots(Loop_1).Location.X = Ship.Location.X
            Shots(Loop_1).Location.Y = Ship.Location.Y
            Shots(Loop_1).Speed = 3
            Shots(Loop_1).TTL = 320
            Shots(Loop_1).First = True
            Exit For
         End If
      Next Loop_1
      Shoot = False
   End If
End Sub

In this sub, you can see that again you use the mathematical functions to calculate the speed and travel direction of the ship. The key aspect of this is that the Ship can point in one direction, and move in another. But, acceleration is only in the direction the ship points.

I've also only allowed up to 20 shots to be fired; this is more a strategic limit that an oversight. Direction of the ship is altered by 1.5 degrees per frame, and the ship's top speed is limited to 2 pixels per frame, although the shots fired travel at 3 pixels per frame.

Acceleration is at 0.02 pixels per frame. This is where using a Single data type comes into play. Using a long would have left you with several longer methods to control the rate of acceleration, or a very high rate of acceleration.

After processing the user's input, you have to now render the ship on your game field.

Private Sub Put_Ship()
Dim Cord(2) As Cords_Type
Dim TL As Cords_Type
With Ship
   .Location.X = .Location.X - .Speed * Sin(.Direction * PI / 180)
   .Location.Y = .Location.Y + .Speed * Cos(.Direction * PI / 180)

   If .Location.X >= 640 Then .Location.X = .Location.X - 640
   If .Location.X < 0    Then .Location.X = .Location.X + 640
   If .Location.Y >= 480 Then .Location.Y = .Location.Y - 480
   If .Location.Y < 0    Then .Location.Y = .Location.Y + 480

   For Loop_1 = 0 To 2
      Cord(Loop_1).X = .Location.X - (.Len_Const(Loop_1) * _
         Sin(.Ang_Const(Loop_1) + .Angle * PI / 180))
      Cord(Loop_1).Y = .Location.Y + (.Len_Const(Loop_1) * _
         Cos(.Ang_Const(Loop_1) + .Angle * PI / 180))
   Next Loop_1

End With
If Not First Then
   Screen.Line (Old_Ship(0).X, Old_Ship(0).Y)-(Old_Ship(1).X, _
                Old_Ship(1).Y)
   Screen.Line (Old_Ship(1).X, Old_Ship(1).Y)-(Old_Ship(2).X, _
                Old_Ship(2).Y)
   Screen.Line (Old_Ship(2).X, Old_Ship(2).Y)-(Old_Ship(0).X, _
                Old_Ship(0).Y)
End If
Screen.Line (Cord(0).X, Cord(0).Y)-(Cord(1).X, Cord(1).Y)
Screen.Line (Cord(1).X, Cord(1).Y)-(Cord(2).X, Cord(2).Y)
Screen.Line (Cord(2).X, Cord(2).Y)-(Cord(0).X, Cord(0).Y)

CopyMemory Old_Ship(0), Cord(0), 24

End Sub

In this sub, you move the location of the ship according to the speed and direction, and wrap it around the screen if it reaches the edge. You now have the screen location of the center of the ship, and the angle that the ship is pointing. Using the constants pre-calculated earlier, you can calculate the new coordinates of the vector points.

Also, you have to check whether this is the first time the ship is been drawn on the screen. If it is, you don't need to blank out the old ship. Blanking out the ship is done by redrawing it with the old vector points.

You now can place the ship in the new location simply by drawing lines to join the three coordinates. The Asteroids are done with a sub similar to the ship, as is the shot's, and there is no need to list them because they are available in the working download.

On the next page, you look at detecting collisions between the Asteroids and shots fired or the ship.

Animation in VB (Part 1)

Although not part of animation, I'll cover the last few bits of the asteroids code. In the case of ship collision with an asteroid, the ship is reset to home position; however, you don't want to place the ship there while an asteroid is floating by.

Private Sub Check_Space()
Dim Diff As Cords_Type
Dim Direction As Single
Dim Distance As Single
For Loop_1 = 0 To 39
   With Asteroids(Loop_1)
      If .Vis Then
         Diff.X = .Location.X - 320
         Diff.Y = .Location.Y - 240
         If Diff.Y = 0 Then
            Direction = (Atn(Diff.Y / Diff.X)) / PI * 180
            Distance = Diff.X / (Cos(Direction * PI / 180))
         Else
            Direction = (Atn(Diff.X / Diff.Y)) / PI * 180
            Distance = Diff.Y / (Cos(Direction * PI / 180))
         End If
         If Abs(Distance) < (50 / .Size) + 40 Then
            Exit Sub
         End If
      End If
   End With
Next Loop_1
Dead = False
First = True
With Ship
   .Speed = 0
   .Angle = 180
   .Direction = 0
   .Location.X = 320
   .Location.Y = 240
End With
Put_Ship
First = False
End Sub

The key part of this code simply calculates the distance from the center of the asteroid to the center of the screen, where the ship will be placed. If this distance, less the size of the asteroid, is less than 40 pixels, the ship will not be reset.

Next, you look at the Asteroid collision code.

Private Sub Hit_Aster()
Dim Diff As Cords_Type
Dim Direction As Single
Dim Distance As Single
For Loop_1 = 0 To 39
   With Asteroids(Loop_1)
      If .Vis Then
         For Loop_2 = 0 To 19
            If Shots(Loop_2).TTL > 0 Then
               Diff.X = .Location.X - Shots(Loop_2).Location.X
               Diff.Y = .Location.Y - Shots(Loop_2).Location.Y
               If Diff.Y = 0 Then
                  Direction = (Atn(Diff.Y / Diff.X)) / PI * 180
                  Distance = Diff.X / (Cos(Direction * PI / 180))
               Else
                  Direction = (Atn(Diff.X / Diff.Y)) / PI * 180
                  Distance = Diff.Y / (Cos(Direction * PI / 180))
               End If
               If Abs(Distance) < 50 / .Size Then
                  Old_Score = Score
                  Score = Score + 5 * .Size
                  Screen.Line (Shots(Loop_2).Location.X - 1, _
                              Shots(Loop_2).Location.Y - 1)- _
                              (Shots(Loop_2).Location.X + 1, _
                              Shots(Loop_2).Location.Y + 1), , BF
                  Shots(Loop_2).TTL = 0
                  Shots(Loop_2).First = True
                  .Size = .Size * 2
                  If .Size <= 8 Then
                     For Loop_3 = 0 To 39
                        If Not (Asteroids(Loop_3).Vis) Then
                           CopyMemory Asteroids(Loop_3).Speed, -
                              Asteroids(Loop_1).Speed, 197
                           Asteroids(Loop_1).Direction = Rnd() * 360
                           Asteroids(Loop_1).Rotate = Rnd() * 2 - 1
                           Asteroids(Loop_3).Direction = _
                              Asteroids(Loop_1).Direction + 180
                           Asteroids(Loop_3).Rotate = Rnd() * 2 - 1
                           Asteroids(Loop_3).Vis = True
                           Asteroids(Loop_3).First = True
                           Exit For
                        End If
                     Next Loop_3
                  End If
               End If
            End If
         Next Loop_2
            If Not Dead Then
               Diff.X = .Location.X - Ship.Location.X
               Diff.Y = .Location.Y - Ship.Location.Y
               If Diff.Y = 0 Then
                  Direction = (Atn(Diff.Y / Diff.X)) / PI * 180
                  Distance = Diff.X / (Cos(Direction * PI / 180))
               Else
                  Direction = (Atn(Diff.X / Diff.Y)) / PI * 180
                  Distance = Diff.Y / (Cos(Direction * PI / 180))
               End If
               If Abs(Distance) < (50 / .Size) + 6 Then
                  Screen.Line (Old_Ship(0).X, Old_Ship(0).Y)- _
                     (Old_Ship(1).X, Old_Ship(1).Y)
                  Screen.Line (Old_Ship(1).X, Old_Ship(1).Y)- _
                  (Old_Ship(2).X, Old_Ship(2).Y)
                  Screen.Line (Old_Ship(2).X, Old_Ship(2).Y)- _
                     (Old_Ship(0).X, Old_Ship(0).Y)
                  Dead = True
               End If
            End If
      End If
   End With
Next Loop_1
End Sub

Instead of trying to check whether a shot has hit one of the vectors or points, you make it very easy. You check the distance between the shots and the asteroid, If this distance is less than the radius of the asteroid, you can assume that the asteroid has been hit.

Effectively, the asteroids and ship collision area are circles around the center of each item. This does raise the issues of: I hit it but it did not explode, or, I missed it but it did explode. However, the user will not notice this, because the tolerance is so close it's hardly perceivable.

If there is a collision, you remove the shot from the screen, reduce the size of the current asteroid, add another asteroid, and send it off in a random direction. Of course, once the size reaches a certain limit, you simply remove the asteroid completely.

Private Sub Check_Stage()
For Loop_1 = 0 To 39
   With Asteroids(Loop_1)
      If .Vis Then
         Exit Sub
      End If
   End With
Next Loop_1
Init_Ast
With Ship
   .Location.X = 320
   .Location.Y = 240
   .Speed = 0
End With
End Sub

Here you check whether all the asteroids have been shot, and reset the screen with a new stage. Then, you place the ship back in the center of the screen.

[Play.jpg]

The game play in this asteroids clone is high paced but simple; the animation is smooth, and flowing. However, the game could use a lot of additional features, including a UFO that fires back, increasing thr number of asteroids for each stage, limited lives, debris on collision, high score table, and a number of other tweaks.

Conclusion

With this being a series of articles covering animation, I will be only partially developing the game in each installment, handling the core animation to the fullest, and adding only enough to demonstrate the animation. Also, there will be suggested additions that can be made to the game so that you can practice the method described yourself.

Next time, you will look at using a sequence of bitmap images to produce animation.



About the Author

Richard Newcombe

Richard Newcombe has been involved in computers since the time of the Commodore 64. Today, he has excelled in programming, and designs. Richard is in his mid 30's and, if or when you looking for him look no further than his computer. Always willing to help and give advice where he can in regard to computer related subjects. At present he is working as a .NET 2008 Software Developer for Syncrony Web Services, South Africa.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Mobile is introducing sweeping changes throughout your workplace. As a senior stakeholder driving mobile initiatives in your organization, you may be lost in a sea of technologies and claims from vendors promising rapid delivery of applications to your employees, customers, and partners. To help explain some of the topics you will need to be aware of, and to separate the must-haves from the nice-to-haves, this reference guide can help you with applying a mobile strategy in the context of application …

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds