4. Elementary AnimationThere are two ways to implement animation for a game in most windows systems One is to take advantage of the time the program spends between user actions, what is usually called idle time. The other is to use multithreading, and, essential, create one or more separate threds for the animation. We will explain these terms as we go along but we warn the reader that some platforms (esp. older ones) do not support multithreading. 4.1. Using Idle Time for MotionThere is a method with a name such as OnIdle() that is called after all messages have been processed and where we can place the code for moving objects. In MFC this method is called from the Application object while the various message and event handlers are called from the view. The following code can be used to invoke the OnGameIdle() method of the Game object.
// Listing A
BOOL CgameApp::OnIdle(long lCount)
{
CWnd * v = CWnd::GetFocus();
if(v != NULL) return (((CChildView *)v)->m_Game).OnGameIdle();
else return FALSE; // v==NULL means focus window
// is not in the application
}
We will not discuss the significance of the argument lCount here because we will not be using it for the time being. If OnIdle() returns TRUE it will be called again, as long as there are no other messages to be processed. When the next batch of messages is processed, OnIdle() will be called again, regardless of what was the earlier return. Let us look inside OnGameIdle(). It invokes a method Step() that was not part of the CBead class we discussed earlier. We could have added this method to the class CBead but a cleaner solution is to extend the class CBead into a new class, CLiveBead, that includes the members necessary for animation. In this case the array m_bead[] contains objects of class CLiveBead and Step() is a method of the object that implements one step of the motion. If there are m_N objects, the code might look like the following.
// Listing B
BOOL CGame::OnGameIdle()
{
for(int i=0; i<m_N; i++) m_bead[i].Step(&m_bounds);
::Sleep(100);
return TRUE;
}
The method Step() implements the shortest possible move of a ball. It takes as argument a rectangle that delimits the motion of the bead. We provide an example below that implements motion under gravity.
// Listing C
BOOL CLiveBead::Step(CRect *rp)
{
CRect r[2], runion;
m_speed.dy += 0.1; // gravity provides constant accelaration
// reverse motion if bead hits boundaries
if(m_pos.bottom + myround(m_speed.dy) > rp->bottom ||
m_pos.top + myround(m_speed.dy) < rp->top) {
m_speed.dy = reverse(m_speed.dy, 0.75);
}
if(m_pos.right > rp->right || m_pos.left < rp->left) {
if(abs(m_pos.bottom - m_screen.bottom) < 3) return FALSE; // stop
else m_speed.dx = reverse(m_speed.dx, 1.0);
}
r[0] = m_pos;
MoveBy(myround(m_speed.dx), myround(m_speed.dy));
r[1] = m_pos;
runion.UnionRect(r, r+1);
DrawArea(&runion);
return TRUE;
}
myround() is a function that finds the nearest integer (positive or negative) to its argument. reverse() changes the sign of its first argument while decreasing its size by multiplying with the second argument. When the bead is at the bottom of the rectangle and meets a vertical side, it does not bounce but the method returns FALSE to end the motion. The above listing of OnGameIdle() does not take advantage of this feature, so we modify as below.
// Listing D
BOOL CGame::OnGameIdle()
{
int k = 0;
for(int i=0; i<m_N; i++) k += m_bead[i].Step(&m_bounds);
::Sleep(100);
return k > 0;
}
|