Simple Animation in Visual Basic (Matt Gertz)
The basics:
You’re almost certainly used to placing controls on a form; however, you probably have an expectation that they will stay in place once you put them there. That need not be the case, however. Controls have X & Y positions, and you can modify them at any time during the runtime. We can use this to create a simple animation in a windows application — let’s say, for example, an animal walking.
First, I’ll need a two or more images of the animal in different stages of walking. (In the attached windows application, I’ve used a couple images of cartoon sheep that I hastily drew up — forgive my lack of artistic abilities here.) The more images you use, the smoother the animations will be.
Everything else I need is already in Visual Basic. I’ll create a new Windows Application and place an image control at the extreme right side of the form (because my sheep starts out facing left). In the resource manager (brought up by right-clicking on the project, choosing “Properties,” and clicking on the Resources tab), I’ll add the two sheep images. (In the attached quick-and-dirty application, I’m cheating slightly – the images are BMPs instead of GIFs, and don’t support transparency. I compensate by making the uninteresting parts of the sheep images the same color as the form’s background.)
Note on transparency in .NET: In .NET forms, transparency is only one level deep, with that level always being the parent form. So, if you put a GIF containing transparent pixels into a PictureBox, the GIF will display correctly on the form (i.e., the form background will show where the transparent bits are). However, if you overlap a PictureBox onto another control, the transparency effect won’t stack – the transparent bits will continue to show the underlying form background, not any parts of the control “in between” them. This can look odd on the screen. In order to support true stackable transparency, you need to handle the OnDraw method of the controls yourself. That’s beyond the scope of today’s subject, though I might address it in a future post.
Now, I double-click the form background to generate the Form_Load event handler. In the handler, I assign one of the sheep images to the image control — when the app runs, you’ll now see a sheep. But we don’t want the sheep to just stand there; we want it to move. How do we do this?
There are two components to the motion in this case. First, we need to move the image control periodically to change the location of the sheep; second, we need to cycle between the two images of the sheep to make it appear that the sheep is responsible for the motion (as opposed to it being dragged by some large unseen feline). The sheep may be either facing left or right on each image, so there are four total states in all:
Enum SheepState
FacingLeftExtendedLeftLegs = 1
FacingLeftExtendedRightLegs = 2
FacingRightExtendedRightLegs = 3
FacingRightExtendedLeftLegs = 4
End Enum
The fact that we need to change states periodically means that we’ll need to add a timer control. I’ll set the timer to fire a tick every 100 ms. (Unlike in my Euchre game example last month, there will be no need to deactivate the timer nor pump messages while handling the event, since we are not interrupting any other logic when the timer goes off.) We’ll turn it on in the form’s load event, and initialize the direction and speed as well:
Dim counter As Integer
Dim state As Integer
Dim speed As Integer
Private Sub Pasture_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
state = SheepState.FacingLeftExtendedLeftLegs
speed = 10 ‘ Pixels per timer tick
SheepImg.Image = My.Resources.sheep ‘ Initial image
Timer1.Enabled = True
End Sub
Now, the rest is simple: in the timer event handler, we simply move the image control to the left by a few pixels, and change the image of the sheep. We’ll also check the location of the image control; if the left side is getting close to zero, we’ll flip both images and move the control to the right instead; if the right edge of the control is near the right edge of the form, we’ll flip the images back and move the images the other direction. In this way, the sheep will appear to walk back and forth across the form. I’ll show two of the four states here (the other two are essentially clones of these two, the only difference being the images used):
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Select Case state
Case SheepState.FacingLeftExtendedLeftLegs
‘ Move the sheep to the left
SheepImg.Left = SheepImg.Left – speed
‘ Switch to complementary image
SheepImg.Image = My.Resources.sheep2
If SheepImg.Left > 5 Then
‘ Plenty of room; extend the other legs
state = SheepState.FacingLeftExtendedRightLegs
Else
‘ Don’t walk off the form — turn around!
state = SheepState.FacingRightExtendedLeftLegs
End If
Case SheepState.FacingRightExtendedRightLegs
‘ Move the sheep to the right
SheepImg.Left = SheepImg.Left + speed
‘ Switch to complementary image
SheepImg.Image = My.Resources.sheep2
‘ Make sure image faces right
SheepImg.Image.RotateFlip(RotateFlipType.RotateNoneFlipX)
If SheepImg.Right < Me.Width – 5 Then
‘ Plenty of room; extend the other legs
state = SheepState.FacingRightExtendedLeftLegs
Else
‘ Don’t walk off the form — turn around!
state = SheepState.FacingLeftExtendedRightLegs
End If
‘ (Etc.)
End Select
End Sub
When combined with the aforementioned other states (see the attached ZIP file for the complete code), the sheep will now walk back and forth across the screen. This is certainly cool to look at for a minute or so, but the sheep isn’t really doing anything — it’s not really reacting to its environment in any fun way. It “knows” what its boundaries are, though, and that’s a start. We’ll now expand on this beginning to create another game, this one involving… gerbils.
Intersections via gerbils:
My wife and I are fairly used to being awakened in the morning by cats leaping upon the bed, both of us having been long-time companions to felines throughout our lives. Generally, the wake-up call is an invitation to congratulate the clever creature for once again having conquered the fierce and vicious catnip toy, the trophy now being proudly displayed in their mouths. So, when William the Cat waltzed into our room one Sunday morning making the appropriate noises and with something in his mouth, I assumed it was just the start of just another bad day for Bob the Catnip Mouse.
Then I looked again, and, oh, dear… it was one of my wife’s gerbils. Yipes! I leaped out of bed and hastily relieved William of his tiny burden. A quick examination revealed that the gerbil was ruffled and extremely soggy with cat saliva (and essentially catatonic with panic), but was otherwise unharmed. It turned out that the gerbils had managed to chew through their plastic cage and were now all free-ranging on the bookshelf where the cage sat. (Apparently, William had noticed this and had jumped up to the shelf to seize one of them.) I grabbed a spare glass aquarium and chucked them all in there; fortunately, all were accounted for. I then spent the rest of the day devising new cages for them that would be a little more gnaw-proof.
Now, I couldn’t let such an incident go by without immortalizing it on the computer somehow. So, today, I’ll walk you through the creation of a “catch the gerbil” game which will add on to the basic principles noted in the “sheep” example.
The idea for this game is to catch a gerbil and put it back into its tank without dropping it on the floor. There are two major issues here:
(1) We need to drag the image representing the gerbil – this will represent more state information that we’ll have to cache.
(2) The gerbil will need to “fall” if released, and where it falls to depends on what it’s over – that’s more state information.
For clarity’s sake, I’m not going animate the motion of the gerbil’s legs when it crawls – you should feel free to extrapolate from the sheep example above. Also, instead of flipping images, I’m going to use two PictureBox controls for the gerbil, each with its own reversed image. (You could certainly use the same control & just flip images like we did in the previous example, but I thought I’d try something different this time just to show how to coordinate it.)
My form starts out with several PictureBox controls already on it:
(1) A bookshelf, ostensibly hanging on the wall.
(2) A tank positioned to look as if it’s sitting on the shelf.
(3) Three books – I actually use the same image for each of those three controls, but just stretch the images to make them look like different books.
The two gerbil images are placed “on” the shelf as well, although I set them both to Enabled = False and Visible = False. I also include a label control which just gives feedback to the user; I won’t discuss that one further in this example. Finally, I include to button controls which will allow me to change the speed at which the gerbils crawls.
The Z-order of the controls (the “depth” of each control with respect to each other) is important. I want to have the gerbil crawl behind the books, but in front of the tank. So, I select each of the book controls, right-click, and choose “Bring to Front.” I then right-click the tank control and choose “Send to Back,” and then repeat this for the shelf control (making the tank next-to-last). The gerbils will, by process of elimination, be in the middle of the Z-order.
With the form set up, the first thing I need to do is to establish the known states:
gerbilMovingLeftOutsideCage = 0
gerbilMovingRightOutsideCage = 1
gerbilFacingLeftBeingDragged = 2
gerbilFacingRightBeingDragged = 3
gerbilMovingLeftInsideCage = 4
gerbilMovingRightInsideCage = 5
gerbilFacingLeftFalling = 6
gerbilFacingRightFalling = 7
gerbilFellOffTheTable = 8
gerbilNullAction = 9
Those should be pretty self-explanatory. I then initialize the state of the game in the form’s load event handler (for brevity’s sake, I’m excluding the “Dim” statements here):
state = gerbilState.gerbilMovingLeftOutsideCage
speed = 5 ‘ Pixels per timer tick
fallRate = 8 ‘ Pixels per timer tick
‘ Level where gerbil is crawling outside of the tank
shelfLevel = gerbil0.Top
‘ Level where gerbil is crawling inside the tank
tankLevel = Tank.Top + Tank.Height – gerbil0.Height – 15
‘ Percent probability that gerbil will change direction, per tick
reverseRate = 10
‘ Percent probability that gerbil will not move at all, per tick
freezeRate = 30
Timer1.Enabled = True
If you read this carefully, you’ll note that I’m planning on adding some randomness to the game. I want the gerbil to act like a gerbil; i.e, stopping to rest some times, changing direction, etc., but with no apparent rhyme or reason. (As far as I can tell, gerbils are totally ruled by Brownian motion.)
As with the sheep example, I need to handle the case where the gerbil is just running around on its own. For example, here’s what happens during the timer tick when the gerbil is moving to the left outside of the tank:
Case gerbilState.gerbilMovingLeftOutsideCage
‘ Let’s figure out what the gerbil will do.
‘ Generate a random number to see if he sits still
If Rnd() * 100 > freezeRate Then
‘ He’s not sitting still. Move him to the left
gerbil0.Left = gerbil0.Left – speed
‘ Does he want to change directions?
‘ If so, change which image is shown & enabled,
‘ and the state.
If gerbil0.Left <= Table.Left Or _
Rnd() * 100 < reverseRate Then
gerbil1.Left = gerbil0.Left
gerbil1.Top = gerbil0.Top
gerbil0.Visible = False
gerbil1.Visible = True
gerbil0.Enabled = False
gerbil1.Enabled = True
state = gerbilState.gerbilMovingRightOutsideCage
End If
End If
Note that, if the gerbil changes direction, I need to switch to the other PictureBox control – I set its coordinates to be the same as the previously active control, and then I switch which control is visible and enabled. Three of the other states (gerbilMovingRightOutsideCage, gerbilMovingLeftInsideCage, gerbilMovingRightInsideCage) use essentially this same logic, varying it only by the PictureBox control used (gerbil0 vs. gerbil1) and the boundaries of motion (constrained to the tank vs. constrained to the shelf).
Unlike the sheep example, though, I need to handle the case where the user drags the gerbil (which, if you think about it, gives a whole new meaning to the phrase “move the mouse”). I want the gerbil to move smoothly with the cursor, so I’ll need to figure out the position of the gerbil with respect to the cursor point, and use that offset whenever the gerbil is moved. The flow is:
(1) When mousing-down, calculate the offset between the top left corner of the gerbil and the cursor.
(2) When dragging, move the gerbil to the cursor, minus the previously calculated offset.
(3) When releasing the gerbil, figure out where it will fall to (the stop height) and what it will be doing when it gets there (the “new state”), cache that information, then switch to a “falling” state.
(4) When done falling (i.e., when cached stop point is reached), switch to the cached “new” state, and resume the game.
I’ll highlight the code for the case where the gerbil is facing left. (The facing-right code would be identical, except the gerbil1 control would be used). So, here we go:
Point 1 retrieves & caches the offsets using the MouseEventArgs passed to the handler, assuming that you’re in a state where you can grab the gerbil (e.g., not when the gerbil is falling, since the gerbil would get scared and bite you):
Private Sub gerbil_MouseDown(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles gerbil0.MouseDown, gerbil1.MouseDown
‘ Figure out where the offset into the gerbil is
If state = gerbilState.gerbilMovingLeftOutsideCage Or _
state = gerbilState.gerbilMovingLeftInsideCage Then
offx = e.X
offy = e.Y
state = gerbilState.gerbilFacingLeftBeingDragged
‘ (etc.)
Point 2 simply moves the gerbil along with the cursor, modyfing it by the offset:
Private Sub gerbil_MouseMove(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles gerbil0.MouseMove, gerbil1.MouseMove
‘ Move the gerbil to the current cursor position with
‘ respect to the cached offset
If state = gerbilState.gerbilFacingLeftBeingDragged Then
gerbil0.Left = gerbil0.Left + e.X – offx
gerbil0.Top = gerbil0.Top + e.Y – offy
‘ (etc.)
Points 3 has the most complicated logic (if you can call it that), as shown in this excerpt, since we need to figure out where it will drop to and what it will do when it gets there:
Private Sub gerbil_MouseUp(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles gerbil0.MouseUp, gerbil1.MouseUp
‘ The user released the gerbil. Figure out where the
‘ gerbil is, and how far it will fall to.
If state = gerbilState.gerbilFacingLeftBeingDragged Then
‘ If we’re above the tank floor, then fall to
‘ the tank floor so that the gerbil end up walking
‘ on the cedar chips inside
If gerbil0.Left > Tank.Left And _
gerbil0.Left + gerbil0.Width < Tank.Left + Tank.Width And _
gerbil0.Top + gerbil0.Height <= tankLevel Then
stopheight = tankLevel
‘ When we’re done falling, new state should
‘ be inside the cage
newstate = gerbilState.gerbilMovingLeftInsideCage
Else
‘ If we are above the shelf, then fall to the shelf
If gerbil0.Top < shelfLevel Then
stopheight = shelfLevel
‘ When we’re done falling, new state should be
‘ on the shelf outside the cage
newstate = gerbilState.gerbilMovingLeftOutsideCage
Else
‘ We must be below the shelf, so fall off the form…
stopheight = Me.Height + gerbil0.Height + 1
‘ When we’re done falling, new state should be
‘ “Oops, you goofed.”
newstate = gerbilState.gerbilFellOffTheTable
End If
End If
state = gerbilState.gerbilFacingLeftFalling ‘ Start falling…
‘ (Etc.)
End Sub
And then back in the timer tick handler, we handle part 4, the gerbilFacingLeftFalling state, as well as the (not shown) corresponding state if it is facing right:
Case gerbilState.gerbilFacingLeftFalling
‘ Keep falling until we reach the stop height
temp = gerbil0.Top + fallRate ‘ Move gerbil down
If temp > stopheight Then ‘ Don’t fall past the stop height!
temp = stopheight
End If
gerbil0.Top = temp
If gerbil0.Top = stopheight Then ‘ Have we fallen enough?
state = newstate ‘ Yes, fall is complete;
‘ switch to cached new state
End If
Finally, we can control the “action” (if I may dignify it with such a term) by handling the fast/slow button clicks, disabling the appropriate button when it’s at the slowest or fastest:
Private Sub Slow_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Slow.Click
‘ Make the gerbil go slower and more likely to sit still
freezeRate = freezeRate + 5
speed = (100 – freezeRate) / 6
If freezeRate = 100 Then
Slow.Enabled = False
End If
Fast.Enabled = True
End Sub
Private Sub Fast_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Fast.Click
‘ Make the gerbil go faster and less likely to sit still
freezeRate = freezeRate – 5
speed = (100 – freezeRate) / 6
If freezeRate = 0 Then
Fast.Enabled = False
End If
Slow.Enabled = True
End Sub
And our gerbil game is complete. OK, so maybe it isn’t Baldur’s Gate or Halo, but it does demonstrate the control you have over the objects on your forms. Enjoy!
Both the sheep and the gerbil examples are included below in the attached ZIP file. Next time, I’ll be continuing the game theme in a new short series, talking about creating objects on the fly as we walk around a virtual house looking for clues. Talk to you then!
–Matt–*
0 comments
Be the first to start the discussion.