Animation

 

Animation is implemented as follows:

 

·       Create a memory DC  for double buffering

 

·       Every so many milliseconds,  update the image in the memory DC to reflect the motion since the last update, and then update the screen during a single vertical retrace interval.

 

The following points are vital:

 

·       The updates must take place at least 30 times a second for smooth animation, because of the physiology of the human vision system.

 

·       The use of double buffering is essential to avoid flicker.

 

 

To do this in .NET,  we use a Bitmap to create the memory DC, and we get a Graphics object to write to that memory DC using the FromImage method provided by the FCL for that purpose.   This is a step beyond the last lecture, in which we only used the SetPixel method of the Bitmap class to write to the memory DC.

 

Timers in .NET

 

We also need to go beyond the last lecture for timing.  In the last lecture, we updated our memory DC in processing the Idle event, and one can also do animation that way, but then the speed of the animation is dependent on the speed of the host machine.  That can wreak havoc with a video game, rendering it impossible to play on a fast machine and boring on a slow machine.   More accurate timing is provided by the Timer class in the FCL.

 

This could not possibly be simpler to use.  (But as a Win32 veteran, I assure you that it could be more complex!)  Just create a new Timer object,  timer1,  set timer1.Interval to the desired number of milliseconds, say 20,  between ticks.  Then timer1 will generate Tick events every 20 milliseconds.   Process those events and do whatever it was you wanted to do every 20 milliseconds.  Just don’t take longer than 20 milliseconds to do it.

 

You can do all this in Visual Studio by dragging and dropping a Timer onto your form in the design editor, and using its property sheet to set the interval and create a handler for the Tick event. The code that Visual Studio writes for you boils down to this:

 

this.timer1 = new Timer();

this.timer1.Interval = 20;

this.timer1.Tick += new

     System.EventHandler(this.timer1_Tick);

 

It may be hard to believe that ten years ago, I would give an entire 50-minute lecture about how to use timers in the Win32 API.  

 

Animating a Rectangle that Grows in Size

 

As a warmup exercise, let’s try to animate something.

Create a new application called Balls (in anticipation of what we will eventually animate). Then add a Bitmap member variable m_theBitmap to serve as the memory DC.   As in last week’s lecture,  write this code:

 

private void resetDoubleBuffer()

{ Graphics g = CreateGraphics();

  if(m_theBitmap != null)

      m_theBitmap.Dispose();

  m_theBitmap = new Bitmap(this.Width, this.Height,g);

}

 

and call it in processing the Resize event:

 

private void Form1_Resize(object sender,

                          System.EventArgs e)

{

  resetDoubleBuffer();

}

 

I found it necessary to call resetDoubleBuffer in the form constructor as well.   The following code snippet show this call, as well as the result of creating a timer.  Note the line ResizeRedraw = true.

 

private System.Windows.Forms.Timer timer1;

private Bitmap m_theBitmap;

public Form1()

{ InitializeComponent();

  ResizeRedraw = true;

  resetDoubleBuffer();

  m_Balls = new ArrayList(50); 

  timer1.Start();

}

 

Now just to get started,  let’s try to get something drawn that changes smoothly every 20 milliseconds.   Set your timer’s interval property to 20,  add an integer member variable ticks, and put in the following handler (from the timer’s property sheet):

 

private void timer1_Tick(object sender, System.EventArgs e)

{ update();

  ++ticks;

}  

 

Before that will even compile, of course, we have to write update.  Start out with the simplest possible thing:

 

private void update()

{ if(m_theBitmap == null)

      return;

   Graphics g = Graphics.FromImage(m_theBitmap);

   g.FillRectangle(Brushes.Black,ClientRectangle);

   g.FillRectangle(Brushes.Red,0,0,50+ticks,50+ticks);   

         //just for testing

   Invalidate();

}

 

The first two lines may be just paranoia:  it seems to work OK without them, but I could not be sure that I might not get an update message before everything has been properly initialized.  I have been burned too many times in the past.  

 

We are hoping to see a red rectangle start in the upper left corner and smoothly expand until it fills the window.  However, that’s not what you will see!    You will see a lot of flickering.  We will fix that problem next.

 

The EraseBackground event

 

We have to go back to a basic point about Windows graphics.  When you call Invalidate,  you have learned that it causes a WM_PAINT message to be sent to your window,  which in .NET causes a Paint event.   But the whole truth is slightly more complicated:  it causes two messages to be sent, in this order:

 

·       WM_ERASEBKGND

·       WM_PAINT

 

The default processing of the WM_ERASEBKGND message is to fill the client rectangle with the background color of the form (you can set that on the form’s property sheet).    The flicker that you see (and that can be seen in simpler programs, such as one that changes the color of a rectangle on a mouse click)  is due to this.  The background color of our form is some light color, not black.  Now, we could try to fix this problem by changing the background color of the form to black,  but that would be cosmetic.  What we want to do, especially in connection with double-buffering, is  to kill all processing of WM_ERASEBKGND.   There is absolutely no need for it: since we intend to use BitBlt to paste an image over the entire client area, it would be a waste of time to first erase the background.

 

Here is how to do that:  add this code (yes, the function has an empty body) to your form:

 

protected override void OnPaintBackground (PaintEventArgs e)

{ // stops processing WM_ERASEBKGND

}

     

You do not see the PaintBackground  event listed in the form’s property sheet under events that can be handled.  Nevertheless, you can add this code, and it works to fix the flicker problem in animating the red rectangle.

 

If you read the documentation carefully, you will find that it tells you that you should cast e to a HandledPaintEventArgs object and then set e.Handled = true to block all default processing of the event.  But, this instruction can’t be followed, and is unnecessary anyway, as the empty-body code above works, even though according to the documentation it should not.   Check it out for yourself:  your red rectangle animation works beautifully.

 

The Balls program

 

Once we can animate a rectangle, we can animate just about anything, and the only further complications will be specific to whatever it is we choose to animate.

 

As an example of animation, the Balls program allows the animation of a number of bouncing colored balls.   The balls bounce off the sides of the client area, but they otherwise move in straight lines and they move transparently through each other, since making them bounce off each other or respond to “gravity” are exercises in physics and algebra rather than in .NET programming.

 

Add an ArrayList called m_Balls to hold the balls that we will animate.   Since this list holds arbitrary objects, we don’t need to define our Ball class before we define m_Balls,  but defining the Ball class is surely the next order of business.  The plan is this:

 

·       Define the Ball class, giving each ball properties such as color, radius, position, and velocity.

 

·       Implement update()  so that it moves each ball to the new position it would move to in timer1.Interval milliseconds, given its present velocity and position;  but if this would take it outside the client rectangle, it must “bounce off the wall” instead,  changing its position and velocity as a ball would when bouncing.

 

Taking these in the opposite order, we implement update like this:

 

private void update()

{

   if(m_theBitmap == null)

      return;

   Graphics g = Graphics.FromImage(m_theBitmap);

   g.FillRectangle(Brushes.Black,ClientRectangle);

   // g.FillRectangle(Brushes.Red,0,0,50+ticks,50+ticks); 

   if(m_Balls.Count > 0)

   {  Ball.UpdateBalls(m_Balls,

                       timer1.Interval,

                       ClientRectangle);

      foreach(Ball b in m_Balls)

         b.Draw(g);

   }

   Invalidate();

}

 

This puts the responsibility for knowing how to update m_Balls in the Ball class,  which seems like the right place for it.   Now the next part of the coding will be in the Balls class.  Here are the fields I used for the Ball class:

 

 

public Color color;

public int x,y;   // position of center

public int p,q;   // x and y velocities;

public int r;     // radius of ball

 

If we wanted to make the balls bounce off of each other as well as the walls, then they should also have a mass field, but that is not used in this version.   I wrote a constructor that takes six arguments corresponding to these fields.

 

UpdateBalls  is very simple since we’re not making the balls collide with each other.  Each ball’s update only depends on that ball, not on the other balls:

 

public static void UpdateBalls(ArrayList balls, int t, Rectangle box)

// update positions and velocities of all the balls to

// reflect the passage of time t (in milliseconds)

{ foreach(Ball b in balls)

    b.Bounce(t,box);

}

 

The actual work has to be done in Bounce.  We first calculate what the new position would be if the walls weren’t there, using the formula distance = rate * time.  Then we check if that new position would make the ball stick out of the box.  If it would,  we reposition it inside the box by as much as it stuck out,  and we reverse the direction of its x-velocity (if it stuck out a vertical wall) and/or its y-velocity (if it stuck out a horizontal wall).   Here’s the code:

 

private void Bounce(int t, Rectangle box)

{ // update position and velocity after t milliseconds

  // including bouncing off the walls of box

     x = (int)(x + p*t/1000.0);

     y = (int)(y + q*t/1000.0);

     int oops = x + r - box.Right;

     if(oops > 0)

          { x -= oops;  // bounce off right wall

             p = -p;

          }

     oops = r - x;

     if(oops > 0)  // bounce off left wall

          { x += oops;

             p = -p;

          }

     oops = y + r - box.Bottom;

     if(oops> 0)  // bounce off bottom wall

          { y -= oops;

             q = -q;

          }

     oops = r-y;

     if(oops > 0)  // bounce off top wall

          { y += oops;

             q = -q;

          }

}

 

 

This code works correctly if a ball goes off-window in a corner:  both horizontal and vertical velocities get reversed, and it gets both a horizontal and vertical displacement. 

 

Once this code compiles, you still can’t test it, because we haven’t yet written any code to create balls.   That’s done back in Form1 by processing MouseDown.    We create a new ball where the mouse was clicked.  But you have a lot of freedom as to how to define the color, radius, and velocity of the ball.  The following code follows these simple rules:

 

·       The colors cycle through red, blue, yellow, and green on subsequent mouse clicks.

·       The sizes cycle through small, medium, and large.   Thus the same size and color repeat only after twelve clicks.

·       The speed of the ball is such that, if it moved straight to the left, it would reach the edge of the window in one second.  Thus fast balls are created by clicking near the right of the window, slow balls by clicking near the left.

·       The direction of the velocity is random.

Here’s the code:

 

private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)

{ // create a new ball with a random direction and

   // speed sufficient to reach the left wall in one second if

   // its direction were (-1,0).  

   double theta =  // a random angle

         m_RandomNumbers.NextDouble() * Math.PI * 2.0;

   int speed = e.X; 

   int p = (int) (speed * Math.Cos(theta));

   int q = (int) (speed * Math.Sin(theta));

  

   Color c;

   int nBalls = m_Balls.Count;

   switch(m_Balls.Count % 4)

      { case 0:  c = Color.Red; break;

        case 1:  c = Color.Yellow; break;

        case 2:  c = Color.Blue; break;

        default:  c = Color.Green; break;

      }

   int radius;

   switch(m_Balls.Count % 3)

      { case 0:  radius = 10; break;

        case 1:  radius = 20; break;

        default:  radius = 40; break;

      }

   int x = e.X;

   int y = e.Y;

   int oops = e.X + radius - ClientRectangle.Width;

   if (oops > 0)

      x -= oops;

   oops = radius - e.X;

   if(oops > 0)

      x += oops;

   oops = e.Y + radius - ClientRectangle.Height;

   if(oops > 0)

      y -= oops;

   oops = radius - e.Y;

   if(oops > 0)

      y += oops;

   Ball b = new Ball(c,x,y,p,q,radius);

   m_Balls.Add(b);

   // don't call Invalidate! 

}

 

All this code is Ball-specific.   The only point of general interest for animation coding is the last comment:  don’t call invalidate!

You are accustomed to calling Invalidate when a mouse-click changes the data, in order to cause the changes to show up on the screen.  But when you are using double-buffering, usually the update of the screen is controlled another way.  In this case, it’s controlled by the timer, so your new ball will show up within 20 milliseconds anyway.   There is no need to disrupt the flow of steady updates every 20 milliseconds by causing another one in between. 

 

Here’s a screen shot of the program; of course, you have to run the actual program to see the balls move.