BREW Breakout - 6 / 9 -
Animation (using timer)
Collision Detection and Animation
In this program, collision detection and animation are done in Void Block::Animate(Void) function. It is invoked by the Void CBAnimate(VoidPtr pdata) function. The CBAnimate function itself will be invoked by the timer. ( Why? We will explain it later.)
Collision detection is done in the front half of the Animate function. Collision detection counts everything as rectangles, then judges if the rectangles are overlapping or not.
This judgment is done by operators defined in Overlap.hpp and Overlap.cpp. Please refer to the source code for the actual processing.
Animation processing is carried out in the bottom half of the Animate function. ( See "Animation" code below )
.... // Animation // Ball DrawBall(false); _usingBall.ball += _usingBall.velocity; DrawBall(true); // Racket DrawRacket(false); _racket.racket += _racket.velocity; DrawRacket(); // Display score and remaining balls DrawMessage(); // Update screen _pgraphics->Update(); ...
Since we have already coded functions for drawing balls and rackets, this function is quite simple.
There are 2 important points in this code.
To move SFXRectangle or SFXCircle object, use += operators. But this "move" does not actually move anything on the display. It implies a change in the coordinates retained by each object.
So the second point is not to forget to update screen.
_pgraphics->Update();
Using the Timer
Animation is done by updating the screen in uniform time segments. In Breakout, the CBAnimate function is invoked every 1 / 50 second (defined in the DRAW_RATE macro of Parameters.hpp). The CBAnimate() function also invokes the Block::Animate(Void) function.
This is the actual code.
... UInt16 flag = CheckEnd(); SFBShellSmp shell = SFBShell::GetInstance(); // After finishing stage with moving block if (flag != STAGE_END_CONTINUE && _blockMotion) { // Cancel the timer for moving blocks shell->CancelTimer(CBMoveBlocks, this); } switch (flag) { case STAGE_END_CONTINUE: // Continue // Set timer shell->SetTimer(_drawInterval, CBAnimate, this); break; case STAGE_END_MISS: // missed ! if (--_spareBallNumber >= 0) { // start the stage again CreateStage(_nowStage); } else { // Game over ShowGameOver(); } break; case STAGE_END_CLEAR: // finish next stage if (++_nowStage <= MAX_STAGE) { // next stage CreateStage(_nowStage); } else { // All stages cleared. ShowGameClear(); } break; default: break; } ...
shell->CancelTimer(...) will be explained later, so do not worry about it now.
One of the member functions of the Block class, CheckEnd(), judges wether to finish the stage or not. Please take a look at this code.
shell->SetTimer(_drawInterval, CBAnimate, this);
This line sets the timer.
There is a member function named SetTimer() in the SFBShell class. This is its definition.
SFCError SetTimer( SInt32 mSecs, // Time until timer ends (msec) PFNNOTIFY notify, // Callback function VoidPtr data = NULL // User data );
What this means, is that after the SetTimer() function is invoked and when mSecs milliseconds have elapsed, the callback function notify(data) is executed. Hence, the callback function must have VoidPtr as its argument.
Argument notify in SetTimer() is a function pointer in Void type. This is the reason why the CBAnimate() function should invoke the Block::Animate() function.
In C++, it is complicated to obtain a pointer to a dynamic member function for certain classes.
Pointers of static member functions can be obtained just like in C. But, member variables used in static member functions must be static member variables.
Static member functions can not be used in BREW. ( On the other hand, if you don't need to access member variables inside, you can use static member variables)
If normal functions are used for callbacks, pass the pointer of an object as an argument. But it can not access unrevealed members within the function.
Making callback functions, friend functions of that object is one way, but when trying to avoid the use of friend functions, it should be coded as follows.
Call back function (for example foo) is defined as,
Void foo(VoidPtr pdata);
When using SetTimer, obtain IShell interface of BREW.
SFBShellSmp shell = SFBShell::GetInstance();
Then write this line.
shell->SetTimer(...);
By the way, this timer executes the assigned call back function only once. So in this code, after CBAnimate function has been invoked by the timer, it is re-registered to timer.
Screen updates are done in uniform time intervals this way.
Using timer 2
There is one more place in Breakout where the timer is used. In the MoveBlocks() function used in stage 3 and 4 to move the blocks. ( See Block.cpp for details )
The next bit of code is about the CancelTimer() in the Animate() function.
...SFBShellSmp shell = SFBShell::GetInstance(); if (flag != STAGE_END_CONTINUE && _blockMotion) { // When finishing stage with moving blocksshell-> // CancelTimer(CBMoveBlocks, this); // Reset timer
In this code, the timer that invokes CBMoveBlocks is canceled. (This is done by registering CBMoveBlocks to the timer and invoking MoveBlocks from the function)
This is the definition of CancelTimer.
SFCError CancelTimer(PFNNOTIFY notify = NULL, // Call back function VoidPtr data = NULL user data);
It won't be a problem if the argument notify is not registered to the timer. The timer itself is defined by a combination of notify and data. If notify is an empty pointer, all timers associated to the data will be canceled.
This process is required for suspend, so please do not forget it.