PROGRAMMING THE BLITTER
BY SAMUEL STREEPER
|AT A GLANCE|
In this third and last column on programming the blitter, I'll build upon the first two lessons, blitting predefined bitmaps and flicker-free animation, and discuss multiple animations.
At this point you have a good grasp of the how to perform a blit. Now I'll show you how to weave those techniques into a program with simultaneously animated objects. The key problem here is tracking the animations; in other words, managing the blits. Since blitter functions are so useful when programming games, I've written one to serve as my advanced blitter example.
BoinkOut is an arcade game that combines elements from Breakout, Arkanoid and Boink, and plenty of its own tricks. The basic idea is to destroy all the bricks by hitting them with the ball, which you control with the paddle, while preventing the ball from dropping to the bottom of the screen.
There are 36 levels, each with a more difficult pattern of bricks. Bricks can be normal, wimpy, magic, multiple, or permanent. Magic bricks have a number of effects, either good or bad, including splitting a ball into multiple balls. Completing all 36 levels is a difficult task, but since BoinkOut runs as either a program or a desk accessory, you can play it a little at a time without losing your place when you need to run another program.
I prefer games with lots of animation; when something is destroyed it shouldn't just disappear, it should do something interesting. In BoinkOut, when you hit a wimpy brick, it fades away. When you hit a permanent brick, it sparkles. Everything is animated and each animation involves a series of blits.
In addition, at least two animations are happening at any given time (a ball and the paddle), or there may be as many as a dozen balls and bricks that must be animated. What I needed was a method of managing multiple animations consisting of multiple blits. I solved this problem with list management techniques.
|BoinkOut demonstrates the use of multiple blits
to create fast, smooth animation
My list is divided into two types of animations, background and foreground. The background animations, basically the bricks, don't move. The foreground animations, the balls, do. I update the background before I update the foreground.
First, I defined a structure that describes an animation, ANI_STRUCT, found in BOINKOUT.H. The field frame holds the blit number, which tells the program which stage of the brick's animation to display when the screen is next redrawn.
When a ball hits a brick, the brick's descriptor is added to the animation list. Each iteration, every animation in the animation list is displayed, by blitting, at its proper position on the screen, and its blit number is incremented. When the brick reaches its final blit number, it is removed from the animation list.
Blitting is what gives BoinkOut its speed and smoothness. You can't pause the game while you redraw the screen graphics; you must change those multiple animations quickly and cleanly. See new-ball() in the source-code file BOUT2.C.
Animating the foreground balls is more complex. BoinkOut has three kinds of balls: a spinning Boink ball, a winking eye, or a spinning Atari fuji. A different C function animates each ball. When adding the ball's descriptor to the animation list, I also needed to indicate which function performed the animation.
It would have been possible to include a value describing that function, and every iteration I could have performed code that looks something like this:
if (value = = 1) animate-ball( ); else if (value = = 2) animate-eye( ); else if (value = = 3) animate-fuji( );
It should be obvious that this is inefficient. I need to make several comparisons every iteration just to figure out which animation function to call. Fortunately, C solves this multiple-function dilemma in an efficient way, using a technique known as function pointers. Instead of a variable value describing one of many functions, I can use a variable function, which looks like this:
extern int ball_ani( ); int (*ani_funct)( ); /* variable pointer to the function */
ani_funct = ball_ani; /* assign the variable to the function that animates a ball /* (*ani-funct)( ); /* Call the function; in this case, ball_ani( )*/
Because of this it's very easy to add another ball of any type to BoinkOut. I simply add a properly-initialized descriptor to the animation list, and the descriptor's animation function will draw the correct ball animation.
For balls, the descriptor is BALL_STRUCT, also found in the file BOINKOUT.H. To see how balls are added and removed from the animation list, see the add_ball( ) and kill_ball( ) functions in BOUT4.C.
Perhaps the most difficult part in writing an arcade game is creating a good collision detection routine. Every game will have its own requirements, but my solutions may give you some insights.
Whenever I move a ball, I must look for collisions with the four walls, the paddle (which moves), and all the bricks. Some collisions are easy to detect: If the ball's x coordinate will be less than zero, the ball collided against the left wall and must bounce back to the right.
A paddle collision is more complex because both the paddle and the ball move. It was necessary to constrain the maximum movement of both objects to ensure that they cannot move through each other without ever colliding. BoinkOut keeps the last two positions of the paddle in order to calculate the paddle speed. Thus, when a ball collides with the paddle, a percentage of the paddle speed is added to the ball, which lets the user apply a little "english" to the ball.
Brick collisions are actually the most complex collisions in BoinkOut. It would be very inefficient to see if any point of a ball collided with a brick, so BoinkOut only checks four key points for possible collisions. Each point is checked to see what brick position (if any) it lies in. If a point lies within a brick position, BoinkOut checks to see if a brick is still there. If it finds one, it assumes a collision occurred. A brick descriptor is then added to the animation list, and the ball is bounced in the appropriate direction, determined by which point collided with the brick. To see how brick collisions are detected and handled, see the bcollide( ) function in BOUT2.C.
BoinkOut has a secret demo mode (also known as cheat mode) which is invoked by closing the BoinkOut window and then reopening it while pressing [Right-Shift]. For a more difficult game (especially with a blitter chip!) BoinkOut has a "Fast Mode" which can be enabled from the menu. Fast Mode is exited by holding down both mouse buttons.
The program runs as either a program or a desk accessory, simply by changing its name from BOINKOUT.PRG to BOINKOUT.ACC. (For more information on writing software that runs as either a program or accessory, see "Accessorize Your Programs" in the October 1989 issue of START.) If BoinkOut is run as a desk accessory, you can get an information box by pressing [Left-Shift] while clicking on its menu entry. The BoinkOut picture files (BOINKOUT.PI?) must be in either the current folder or a folder named \BOINK.
BoinkOut was compiled with Laser C, although it should not be difficult to make it work with other compilers. Redefining LASER and MWC in the BOINKOUT.H file should allow Mark Williams C to compile it very easily.
I hope you find BoinkOut to be an enchanting game as well as a useful example program. Happy coding!
Sam Streeper, co-developer of SGS Net, lives in Palo Alto, Calif., and works for NeXT Computers.