REALbasic University Resources:

RBU: Glossary Defines common REALbasic programming terms
  Archives Previously published columns
Translations: Dutch Courtesy of Floris van Sandwijk
  Japanese Courtesy of Kazuo Ishizuka
  Chinese Courtesy of Dong Li
  RBU Translation Guide Information on Translating RBU into other languages
Books: Matt's Book (2nd Edition!) Ideal for experienced programmers
  Erick's Book Best for beginning programmers
Websites: Mother Ship The publisher of REALbasic
  RB Webring Links to hundreds of REALbasic websites
  RESExcellence Another REALbasic programming column
  REALbasic Developer Magazine The premiere source for REALbasic instruction.

REALbasic University is Sponsored by

Make your Mac do what YOU want it to. Create games, utilities, cool Mac OS X tricks. Download REALbasic now and create your own software.


Print This Article

REALbasic University: Column 020

REALbasic Animation: Sprites Part IV

Our program is really coming along, but now we've got to write the most complicated part: the NextFrame event. This is the core of the program: all this code will be executed at 30 frames per second (if you set spriteSurface1's frameSpeed to 2).

Before I give you the complex code, let me explain a bit about what we need to do in this routine. Our basic goals are the following:

  • Move all "living" ducks
  • "Wrap" around any ducks that have moved off the screen
  • Kill off any explosions with an exploding value greater than 30
  • If there's a missile moving, move it
  • Move the player's ship if the player is pressing the left/right arrow key
  • Fire a missile if the player presses the space bar
  • Stop the game if the user presses the Escape or Return key
  • Stop the game if there are no ducks left

Whew! That's a lot of stuff to do. You can see how this could get complicated in a more sophisticated game!

It's not so bad if we take each step piece by piece, though. Here's the first part of the code:

   dim i, j, x, y as integer 

//
// This is the program's main routine. Here is where the
// ducks move, the user shoots a missle or moves the
// ship, etc.
//

j = 0
for i = 1 to numberOfDucks
// Is the duck alive (visible)?
if duckSprite(i).visible then
j = j + 1

// Here we move the sprite right (+) or left (-) and wrap it around
// if it's moving off the screen.
if duckSprite(i).direction then
duckSprite(i).theSprite.x = duckSprite(i).theSprite.x + duckSprite(i).speed
if duckSprite(i).theSprite.x > me.width then
duckSprite(i).theSprite.x = 0
end if
else
duckSprite(i).theSprite.x = duckSprite(i).theSprite.x - duckSprite(i).speed
if duckSprite(i).theSprite.x < 0 then
duckSprite(i).theSprite.x = me.width
end if
end if // duckSprite(i).direction
else
// We get here if the sprite is hidden. We check to see if it's
// in the process of exploding, and if so, increment the explosion
// counter. When the counter is greater than 30, we terminate
// the sprite.
if duckSprite(i).exploding > 0 then
duckSprite(i).exploding = duckSprite(i).exploding + 1
if duckSprite(i).exploding > 30 then
duckSprite(i).exploding = 0
duckSprite(i).theSprite.close

drawScore
end if
end if
end if // visible
next // i

This first part is just a big loop through every duck. We check to see if the duck's visible (alive) or not, and if it is alive, we move it. We move it left or right according to the duck's direction property, and the amount we move it -- the number of pixels -- is the speed value.

After we've moved the duck, we check to see if we moved it too far so it's off the screen. If so, we move it to the opposite end so it "wraps around" and starts over from the other side.

If our duck is "dead" (hidden), we check to see if it is exploding. (If the exploding property is greater than one, we know it's exploding.) We increment the exploding property. If it's greater than 30, then enough time has passed for the explosion to be seen, so we terminate the sprite and set its exploding property to zero.

Our final step is to draw the score after the explosion has gone away.

The next bit of code directly follows the above:

   if j = 0 then // all ducks shot, game over 
gameOver = gameOver + 1
if gameOver > 30 then
me.close
end if
end if

// Here we move the missle. Note it moves 5 pixels at a time, much
// faster than the ducks.
if missileLaunched then
shot.y = shot.y - 5

// Has the shot gone off the screen? if so, terminate it.
if shot.y < 0 then
shot.close
missileLaunched = false
end if
end if

First we see if the all the ducks have been shot. If so, we stop the execution of spriteSurface1 -- that's the me.close line-- there's no point in continuing if there are no ducks to shoot.

Next, just like we move the ducks with each frame of animation, we now move the missile (if there is one). The ducks move only horizontally while the missile moves vertically, so for the ducks we added or deleted numbers to the x property. With the missile, we work with the vertical -- y -- property, and we only subtract since the missile only moves upward. Since we want the missile to move quickly, we move it 5 pixels at a time. (Feel free to change this and see how that changes the gameplay for you.)

Finally, we check to see if the missile has gone off the top of the screen (obviously without hitting anything). If it has, we delete it and set missileLaunched to false.

This routine is a long one, but breaking it into a few steps helps. This final part handles player interaction.

The SpriteSurface object is a bit strange: since it's running in its own little world, you can't use a window's normal KeyPressed event to see if the user pressed a key. Instead you must ask SpriteSurface if the user has pressed a particular key. But even then SpriteSurface is strange: you can't just ask it if a particular ASCII character has been pressed -- you have to instead pass it a hexadecimal keycode.

Keycodes are unique to keyboards: the French keyboard thus uses slightly different keycodes for certain letters than the English keyboard!

Here's a chart showing the hexadecimal keycodes for the English keyboard:


[Click for full view]

Here's the code for the user interaction section:

    
//
// This portion handles user interaction.
//

// Left arrow pressed.
if spriteSurface1.keytest(&h7B) then //left
if direction = 1 then

// Buildup is used for acceleration: the longer
// the user holds down the key, the faster
// the ship moves (to a max speed of ten).
buildup = buildup + 1
if buildup / 10 = buildup \ 10 then
speed = speed + 1
if speed > 10 then
speed = 10
end if
end if
else
// We were going the other direction, so down
// we switch and reset speed and buildup values.
speed = 1
buildup = 1
end if

// Move ship to the left; wrap if off screen.
shipSprite.x = shipSprite.x - speed
if shipSprite.x < 0 then
shipSprite.x = me.width
end if
direction = 1

// Right arrow pressed.
elseif spriteSurface1.keytest(&h7C) then //right
if direction = 2 then

// Buildup is used for acceleration: the longer
// the user holds down the key, the faster
// the ship moves (to a max speed of ten).
buildup = buildup + 1
if buildup / 10 = buildup \ 10 then
speed = speed + 1
if speed > 10 then
speed = 10
end if
end if
else
// We were going the other direction, so down
// we switch and reset speed and buildup values.
speed = 1
buildup = 1
end if

// Move ship to the right; wrap if off screen.
shipSprite.x = shipSprite.x + speed
if shipSprite.x > me.width then
shipSprite.x = 0
end if
direction = 2

// Space bar pressed.
elseif spriteSurface1.keytest(&h31) and not missileLaunched then //space (fire missle)

// Create a new missle sprite and start it going.
missileLaunched = true

x = shipSprite.x + (shooter.width / 2) - (missile.width / 2)
y = shipSprite.y - missile.height
shot = SpriteSurface1.NewSprite(missile, x, y)
shot.group = -1
if soundOn then
zap2.play
end if
score = score - 25
drawScore

// return or Escape pressed.
elseif spriteSurface1.keytest(&h24) or spriteSurface1.keytest(&h35) then //quit
me.close
end if

Most of this code is very similar to what we've done before: we move a sprite by adding and subtracting to its x and y properties, and we wrap it around the screen if it goes too far.

One new twist is the buildUp variable. Again, this is subtle detail that I wanted my game to implement. Basically, I wanted the movement of the player's ship (or gun) to accelerate: the longer the player holds down the arrow key, the faster the ship moves (up to a point).

Now I could have just incremented the speed value, but since this happens every time nextFrame is called (at least 30 times per second), that would happen almost instantly (less than a second). So instead I use a second value, buildUp, and I increment that during each frame of animation. When buildUp is evenly divided by ten (every ten buildUps) I then up the speed one notch. Thus in one second (assuming 30 fps animation), the speed only goes up by 3. Since the maximum speed is 10, it takes 3.3 seconds to achieve maximum speed. The result is a gradual acceleration of the player's ship.

If the player changes direction, speed and buildUp are set back to 1. The flaw (bug?) in this implementation is that the above code makes no provision for resetting the user's speed if the arrow key is released. The player can therefore go to maximum speed and as long as the player doesn't change direction, always move at maximum speed.

In Detail

In REALbasic, the slash acts as a divide command. So 5 / 10 = 0.5. The backslash is also a divide, but it's an integer divide: it returns only the integer portion of the result (it ignores everything to the right of the decimal point). Therefore 5 \ 10 = 0.

So to see if buildUp is evenly divided by 10, I use the following if statement:

 if buildup / 10 = buildup \ 10 then 

Our next bit of code check to see if the player pressed the space bar to launch a missile. This is only allowed if a missile isn't already in the air: a more complex game could allow the user to shoot multiple shots at once. Then you -- the programmer -- would have to keep track of multiple shots (probably with an array of missiles, similar to the current array of ducks).

To launch the missle we simply create a new sprite and set its initial location. Once it's created, our previous code will automatically move it starting with the next frame of animation.

Finally, we finish by checking to see if the user terminated the game by pressing either the Escape or Return keys. Neither key quits the program -- they just halt spriteSurface1 and abort the game in progress. (Obviously, in a real game, you'd want to confirm this with the user before stopping.)

Well, guess what? We're finished! That's it for Shooting Gallery. I hope you enjoyed it and learned a little about sprites. Like I promised, we'll do something more elaborate in the distance future, but this should get you started and help motivate you into writing your own animation-based game.

The complete, finished project file with all the required pictures and sounds is available for download.

Experiment with it: change some of the variables and try different things. Enhance it, make it play better. For instance, how about letting the player's "ship" (gun) move vertically as well as side to side? What about making the ducks drop bombs and/or dive downward? How about different kinds of ducks? Instead of the game ending when all the ducks are shot, why not advance the player to a tougher level?

There's plenty more to do: I've just given you a step in the right direction!

The Week After Next Week: Next week I'm off on vacation, so have a great Fourth of July holiday. When I come back, we'll have a look at REALbasic Basics. Some of our tutorials have been a bit advanced, so we'll have something special for the newbies in the audience.

Letters

Remember how last week I mentioned there was an alternate method of animating a spriteSurface -- calling its update method repeated via a timer control? Well Sarah tried that and wrote:

Hi Marc,

I tried implementing the Timer for updating the sprite surface instead of running it but it didn't help. It was also a lot slower than running the spritesurface - I had to double the speed of the ducks and the shot to make it playable even with the timer supposedly calling the spritesurface update every millisecond! The OS X crash still happens when some ducks are shot (not every duck). It seems to happen when a collision occurs but before it gets to the collision handler. The program also crashes sometimes when I move the ship.

Now to my question: how can you know that the shot will be s1 & the duck will be s2 in the collision handler? Shouldn't you be testing both s1 & s2 for their groups?

Interesting info, Sarah. Thanks for the update. I can only surmise that sprites are problematic under Mac OS X for now, though I'm sure REAL Software will eventually fix the problems. I'll have to play with the update method myself at some point: I like the idea of the better control, but Timers are notoriously unreliable for precision timing.

(To see what I mean, start a Timer doing something every second, like moving a staticText. Then pull down the Edit menu of your running program. The Timer doesn't fire. See? Timers don't get called when the Mac is too busy with other stuff. Of course that may only apply under Mac OS -- OS X could be different.)

As to the second part of your question, you got me! One certainly shouldn't know which of the two sprites is which: theoretically either could be the missile. But in practice, it's always s1. Why? I don't know. But when you switch the two around and check for s2 being the missile, it never is. It could be something along the lines that the missile sprite was created more recently and therefore it is the one that's listed first.

I did notice that bug and thought about changing it, but the deadline was looming and I half wondered if anyone would notice: I ought to give your sharp eyes a prize! While the way I did it works, it's not a good programming technique. Never depend on a fluke of your programming environment, even if it consistently works. The bug or oddity might be fixed in a new release of REALbasic and then your program will suddenly start crashing and you'll be driven nuts trying to figure out why.

Christoph Scherer also wrote in with a sprite issue:

Hello,

Any idea if the "update" method for the spritesurfaces would prevent an error like that in my game-prototype ??? When missiles were fired at the enemy and some are left over, they home in on the next dude (as wanted) and all, but they won´t collide. I set a debugging-breakpoint into the "special action/tab" check in the "nextframe" event, just to look what the sprites exactly are when they should collide. Guess what: all values, even group, priority and stuff are correct, so they could collide technically...

I´m getting really desperate. These stupid errors are frustrating, especially when you looked everywhere...

I wish I could be of more help, Christoph. I downloaded your prototype game -- the graphics are very impressive -- but I couldn't really tell much from that. I'd probably need to poke around in your actual source code to see if I saw anything. I did notice that the game was extremely crash prone on my Mac OS 9 system. That could be because it's a Carbon version, which also could be part of the problem. Do you have the same problems in a straight PPC version? (It's been my experience so far that Carbon RB apps tend to be slower and buggier than PPC ones.) Your application wouldn't let me enlarge the memory partition, so I couldn't tell if the problem was low memory.

It could also be that your game is just too complicated: how many sprites did you have going at once? Maybe if there are too many REALbasic doesn't handle the collision issue well.

If anyone else has ideas for Christoph, let me know. It also might be fun to point out some great REALbasic animated games: if you have any you've written or know about, send me the URL and I'll post a link to it next column.


About the Column
REALbasic University is a weekly instructional column on programming with REALbasic and is brought to you by REALbasic Developer, the magazine for REALbasic programmers.

Each week we answer select reader questions, and we're always open to ideas for future columns. Send your questions to . (Keep your questions simple and specific. General queries like "How do I write my own web browser?" will be neglected.) Your question won't be answered immediately, but will be answered in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise it's fair game.)

About the Author
is an author, philosopher, graphic designer, photographer, film director, soccer fanatic, and programmer (among other things). He writes for MacOpinion, runs his own software company, Stone Table Software, which sells the revolutionary Z-Write word processor, and is Publisher and Editor of REALbasic Developer. He lives in Northern California with his cats, Mischief and Mayhem, and is rapidly running out of free time.

See the REALbasic University Archives


REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.

Email This Article - Comment On This Article

.

Reader Specials

Server Racks Online:
Apple Xserve CompatibleServer Racks and Universal Network Racks
42U KVM Switch Solutions:
High-End Mac and Multi-Platform KVM Matrix switching solutions!
Digital Camera Online:
Great prices on Digital Cameras and accessories!
KVM Switches Online:
Great prices on Mac KVM Switches from the leading manufacturers!
LCD Monitors Online:
Great prices on LCD Monitors from the leading manufacturers!
LCD Projectors Online:
Shop online for LCD Projectors from the leading manufacturers!
USB 2.0 Online:
Great prices on USB 2.0 products from the leading manufacturers

Serious Business Software:
Accounting, Sales, Inventory, CRM, Shipping, Payroll & more!

KVM Switch solutions for MACs:
DAXTEN is a KVM switch, KVM extender and monitor splitter specialist for PC, SUN and MAC applications from name brand manufacturers - offices worldwide.

The "Think Different Store: The iPod Accessories Store - iPod cases, iPod mini, iPod photo, speakers, itrip, inMotion, Soundstage and all other iPod accessories

Earn Cash with the ThinkDifferent Store Affiliates Program

Need A Web Site?
Applelinks Web Hosting Starting at 19.95 a Month

iTunes_RGB_9mm

.

iTunes_RGB_9mm

Cool Mac Gear


iPod 1G-2G
iPod 3G
iPod 4G
iPod Mini
PowerBook-iBook
Keyboard Skins
Garageband