The Art of Using Animated Sprites for 2D Games

Overview

In the old days, before video games ran in 3D, all game graphics were hand-drawn and displayed on the screen as static, non-moving objects or as animated sprites. Although the subject of sprites might seem dated and irrelevant today, the truth is quite contrary. Numerous commercial games are released each year that run in 2D using graphics very much like the sprites used in classic arcade games such as Heavy Barrel and Akari Warriors.

Consider Sid Meier's Civilization III, for instance. That game has some fantastic 3D models in it, but the models are all pre-rendered and stored as snapshots to be drawn on the screen as sprites. The game is designed as an isometric turn-based strategy game, and simply does not need to run in 3D mode. Yet despite the fact that this game is 2D, it is a phenomenal best-selling game.

Why do you suppose anyone would develop a 2D game in today's world of advanced 3D video cards? The argument might be made that games have not been able to tap the potential of the latest generation of video cards, and yet new graphics chips are developed every six to twelve months, often doubling the performance of the previous chip. The reason is not that 2D games are easier to program or that 3D models are difficult to design. In fact, Civilization III uses 3D models in a 2D fashion. It is a matter of game play. There are some games that are meant to be played a certain way, and Civilization III just wouldn't be a Civilization game if it didn't run in 2D. This chapter explains not only how to use sprites, but also why you would want to learn about sprite programming in this modern era of 3D gaming.

Introduction to Sprites

A sprite is a small, two-dimensional bitmap that is drawn to the screen at a specified position and usually with transparency. There are many ways to create a sprite and many ways to program its functionality. Will a particular sprite be a solid image or will it have transparent pixels? Will the sprite automatically move in a specified way on the screen, or will it respond interactively to the player? Before these questions even arise, however, how do you create a sprite and load the graphic image (or images) it uses? This chapter addresses these questions.

Transparency

Transparency in a sprite is defined by pixels in the source bitmap that are set to a certain color. When the sprite is drawn on the screen, any pixels with that transparent color are not drawn, which results in the background showing through the sprite in those places. Figure 11.1 illustrates this concept.

Figure 11.1: The dark circle represents the pixels used by the sprite, and the lighter color around the circle represents the transparent pixels.

If you were to load this particular sprite and draw it transparently, the transparent pixels around the ball would not be drawn, so you would only see the ball. However, if you were to draw the sprite without transparency, then all the pixels in the sprite's bitmap image would be drawn, as shown in Figure 11.2.

Figure 11.2: When a sprite is drawn over a background image on the screen, it becomes clear how helpful transparency is for displaying the sprite properly.

Indeed, transparency is the key to drawing sprites, and is essential for any decent 2D game. Without transparency, there really is no possible way to write a game. When you get into the 3D realm transparency takes on a whole new meaning, but when it comes to 2D games transparency is the single most important factor.

Basic Sprite Functionality

Classic video games used sprites exclusively for the graphics and action in the game. In fact, some of the earliest games used solid sprites without any transparency at all! For example, consider the classic Namco game of Pac-Man (circa 1980).

Pac-Man

As Figure 11.3 shows, there was really no need for transparent sprites in Pac-Man because the background in the game is black. Although there are multiple levels in the game, each level is similar in design to the one shown in Figure 11.3. (In fact, I believe Pac-Man and the ghosts were drawn transparently over the yellow pellets, but you get the point). Incidentally, Pac-Man was originally called Puck-Man, but it was renamed shortly after release.

Figure 11.3: Pac-Man epitomized the term "video game" in popular culture.

Galaxian

Another classic videogame, Galaxian, actually had a starry background that required the use of transparent sprites (see Figure 11.4). Incidentally, Galaxian is reportedly the first color arcade game, although I don't have proof for the claim.

Figure 11.4: Galaxian was one of the first space shooters.

Now look at the layout of the screen in Galaxian. Note the rows of enemy ships arrayed at the top half and the player's ship at the bottom of the screen. The enemy ships move left to right and launch an assault on the player. This game provides a clear example of how sprites are used. Each enemy ship, the player's ship, the projectiles, and even the score is made up of small animated sprites, each with perhaps one to three frames of animation (if any). Even the numerals 0 to 9 are just small sprites. Note also how everything on the screen is basically the same size—a square of what looks like 2424 pixels.

Figure 11.5 shows a close-up of the player's ship in Galaxian, made up of 576 pixels (that's 2424). By just counting the sprites on the screen, it looks like the game can handle 90 to 100 sprites at a time. That means the game is drawing 57,600 pixels per frame, plus perhaps 200 more pixels for the background stars. You can round off that figure to 58,000 pixels.

Figure 11.5: The player's ship in Galaxian is a 2424 sprite.

To get an idea of the processing power of the computer that runs Galaxian, you have to take into account the color depth of the screen. Although you can't discern color in the printed figure, it looks to me like there are 16 colors (at most) on the screen. Each sprite has three or four colors, shared by other sprites. Here are the colors that I can discern:

What's that, only seven colors? Compared to today's games, which support thousands or millions of colors, it's extraordinary that Galaxian only used seven colors, and yet it was still a very entertaining game! Given the total number of sprites supported on the screen at a time, it's a safe bet that this was an 8-bit game (at most).

Figure 11.6 shows the relationship of bit count to total value in a video game. To calculate a binary number, start at the right and "turn on" each bit, adding the values until you get the total number. For instance, since Galaxian needs only seven colors, it could have just 4-bit graphics hardware.

Figure 11.6: Bit count related to the value of each bit

Today, bit counts are so high and graphics cards are so powerful that each pixel in a game can be represented by as many as eight bytes for things such as alpha blending, which is a means to apply translucency effects to 3D surfaces. An individual texture used in a 3D model today can take up more memory than an entire arcade game machine of the 1980s!

Galaga

Galaga was also an extremely popular game in the arcades because it was not only challenging, but it also offered more than the usual space shooter. By allowing your ship to be captured by the tractor beam of one of the alien ships, you had an opportunity to free that ship and give yourself double firepower! Figure 11.7 shows Galaga in action.

Figure 11.7: Galaga featured fast gameplay, 16-color graphics, rotating sprites, and terrific sound.

Extending the Basic Bitmap

Referring to Civilization III once again, I'd like to emphasize that this game is a perfect example of a modern 2D game—something that is unusual in today's world of advanced 3D accelerators. Civilization III is a turn-based strategy game that does not use any real-time effects or animation.

The remarkable thing about Civilization III is that it is such an engrossing game despite the somewhat limited graphics. Now, don't get me wrong—Civilization III looks very good, but the game simply does not push the limits of graphics technology. Given the lack of real-time effects in a turn-based game, why is Civilization III so much fun and so utterly mesmerizing? In a word, gameplay. Civilization III puts the player in control of an entire civilization of people in competition with the other civilizations of the world.

The core aspects of the game, such as researching technology, improving the quality of life, and developing weapons and defenses, all take time to develop over a period of turns. It is this progression of development that makes Civilization III such an addictive game.

The Importance of Transparency

Transparency was the primary bottleneck in video game design in the past, and it was often the most highly optimized piece of code in a game. Sprites are drawn to the screen using a technique called bit-block transfer, or BITBLT. The most common shorthand for this term is "blit" or "blitting." It means that blocks of memory are copied from one location to another as quickly as possible, often using a highspeed loop written in assembly language (the closest thing to machine language). Although reusable sprite libraries were available for various platforms (Atari, IBM PC, Amiga, and Mac), most programmers had their own optimized blitters and improved the code for each new game project. Figure 11.8 shows an illustration of transparent pixels in a sprite.

Figure 11.8: A sprite, complete with a bounding rectangle, transparent pixels, and the sprite's pixels

In recent years, however, a single sprite library has been used more than any other for the Windows platform (the world's primary gaming platform). This library is called DirectDraw, and it was available in DirectX up through version 7.0 (which is what DarkBASIC supports, although DarkBASIC Pro uses DirectX 8.1). Since most of the work in a game in past years involved writing the blitters, sound mixers, and so on, the advent of DirectX significantly improved the quality and reusability of game code, allowing programmers to focus on higher-level game functionality, such as physics and more intelligent computer players.

Creating and Loading Sprites

DarkBASIC has built-in sprite support, so there is no need to write any custom code for loading or drawing sprites or testing collisions. I'll talk more about this a bit later, but first you need to load the source image that the sprite will use. An image is like a bitmap in memory. To load a bitmap into a sprite, you must first load a source file into an image and then copy it to the sprite.

The LOAD BITMAP Command

The first step to drawing a sprite on the screen is to use the LOAD BITMAP command to load the image in a bitmap file into a DarkBASIC bitmap (any number from 0 to 32). Remember, the first bitmap, number 0, references the display screen. You can load a bitmap and display it on the screen in a single command by loading the bitmap directly into bitmap 0. The syntax of the command is LOAD BITMAP Filename, Bitmap Number.

The GET IMAGE Command

The GET IMAGE command grabs a piece of a bitmap for use in a sprite, and is most useful for ripping out of a bitmap tiles that are laid out in sequence for an animated sprite. This command is used along with LOAD BITMAP and the SPRITE command to animate a sprite on the screen. The syntax of the command is GET IMAGE Image Number, Left, Top, Right, Bottom.

The SPRITE Command

The SPRITE command draws a sprite on the screen (or the current bitmap surface) using a specified position and image. When it has been drawn, the properties of this sprite are applied to the specified sprite number. The syntax of the command is SPRITE Sprite Number, X, Y, Image Number.

The LoadSprite Program

To demonstrate how to use the preceding commands, I have written a short program called LoadSprite, which loads a bitmap file into memory, copies it into an image, and then draws a sprite. Figure 11.9 shows the output from the program.

Figure 11.9: The LoadSprite program demonstrates how to load a bitmap, copy it into an image, and then use it to draw a sprite.

'----------------------------------- 'Beginner's Guide To DarkBASIC Game Programming 'Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith 'Chapter 11 - LoadSprite Program '----------------------------------- 'Initialize the program HIDE MOUSE CLS 'Load the source bitmap file LOAD BITMAP "F15.bmp", 1 'Grab image 1 from bitmap GET IMAGE 1,0,0,300,300 'Draw the sprite SPRITE 1,170,90,1 'Wait for keypress WAIT KEY END

  Tip

The F-15 Eagle bitmap image used by the sprite in this program is actually a 3D model from the DarkMATTER collection. I simply used the DarkMATTER Browser to view the F-15, rotated it to the appropriate top-down view, took a screenshot of the browser using Alt+Print Screen, and pasted it into my graphics editor. It is fantastic as a 3D model or a sprite!

The TransSprite Program

Although it is not evident in the LoadSprite program, DarkBASIC actually handles transparency automatically for you. All you need to worry about is setting the border color around a sprite to black—an RGB setting of (0,0,0) in your graphics editor of choice (such as Paint Shop Pro, which is very programmer-friendly). To see transparency in action, you need to load a background image and then draw the sprite over the background. Otherwise, the background is just black (as in the LoadSprite program). Of course, you could also just set the background color to something other than black, but a bitmap background is more interesting. Figure 11.10 shows the output of the TransSprite program.

Figure 11.10: The TransSprite program demonstrates how DarkBASIC automatically handles sprite transparency.

'----------------------------------- 'Beginner's Guide To DarkBASIC Game Programming 'Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith 'Chapter 11 - TransSprite Program '----------------------------------- 'Initialize the program HIDE MOUSE CLS 'Load background bitmap LOAD BITMAP "background.bmp", 0 'Load the source bitmap file LOAD BITMAP "F15.bmp", 1 'Grab image 1 from bitmap GET IMAGE 1,0,0,300,300 'Draw the sprite SPRITE 1,170,90,1 'Wait for keypress WAIT KEY END

Sprite Properties

There are some useful commands that return information about a sprite, such as the image number, width, and height. In addition, several commands allow you to manipulate the way in which a sprite is drawn to the screen.

The SPRITE IMAGE Command

The SPRITE IMAGE command returns the image number used by the specified sprite. The syntax of the command is Return Value = SPRITE IMAGE (Sprite Number).

The SPRITE WIDTH Command

The SPRITE WIDTH command returns the width of the image currently in use by the specified sprite. The syntax of the command is Return Value = SPRITE WIDTH (Sprite Number).

The SPRITE HEIGHT Command

The SPRITE HEIGHT command returns the height of the image currently in use by the specified sprite. The syntax of the command is Return Value = SPRITE HEIGHT (Sprite Number).

The OFFSET SPRITE Command

The OFFSET SPRITE command changes the horizontal and vertical offset of a sprite's origin. The default origin is the top-left corner of the image, but this command allows you to change the offset so that the sprite draws from the center point or from some other corner.

The SPRITE OFFSET X Command

The SPRITE OFFSET X command returns the horizontal offset or shift value of the sprite referenced by the sprite number that is passed as a parameter to the command. The syntax for the command is Return Value = SPRITE OFFSET X (Sprite Number).

The SPRITE OFFSET Y Command

The SPRITE OFFSET Y command returns the vertical offset or shift value of the sprite referenced by the sprite number that is passed as a parameter to the command. The syntax for the command is Return Value = SPRITE OFFSET Y (Sprite Number).

The SPRITE EXIST Command

The SPRITE EXIST command returns a 1 if the specified sprite (passed as a parameter) was previously created with the SPRITE command. If the sprite does not exist, the command returns a 0. The syntax for the command is Return Value = SPRITE EXIST (Sprite Number).

Drawing and Moving Sprites

When you are dealing with sprites, the most important commands involve moving and drawing them. Rather than provide separate commands for these functions, DarkBASIC combines the two in a single command called SPRITE. I'll go over SHOW SPRITE, HIDE SPRITE, and several other useful sprite commands in this section.

The SPRITE Command

The SPRITE command draws a sprite using the specified position and image number, and stores the settings for that sprite number. The syntax of the command is SPRITE Sprite Number, X, Y, Image Number.

Finding the Position of a Sprite

The SPRITE command simultaneously moves and draws a sprite using a specified image number, but once the sprite has been drawn, how do you determine its position? This is important when you are dealing with sprite collision. (I'll discuss that in more detail later.)

The SPRITE X Command

The SPRITE X command returns the current horizontal position of the sprite referenced by the passed sprite number. The command syntax is Return Value = SPRITE X (Sprite Number).

The SPRITE Y Command

The SPRITE Y command returns the current vertical position of the sprite referenced by the passed sprite number. The command syntax is Return Value = SPRITE Y (Sprite Number).

The SET SPRITE Command

The SET SPRITE command is very important because it lets you change both the background restoration and the transparency properties of the sprite. The default for these values is 1, which means that the background is saved and restored when the sprite is moved, and transparency is enabled so that black pixels are not displayed with the sprite. Setting the background parameter to 0 will disable the background-saving property, which means that the sprite will "smear" across the screen, and it is up to you to restore the area under the sprite. The syntax of the command is SET SPRITE Sprite Number, BackSave State, Transparency State.

Making Sprites Visible or Invisible

The following commands are used to make a sprite visible or invisible, and apply to the normal sprite-drawing commands.

The SHOW SPRITE Command

The SHOW SPRITE command shows the specified sprite (passed as the sprite number parameter) that was previously hidden using the HIDE SPRITE command. This command does not change the position of the sprite, but simply draws it at the current X and Y position using the current sprite properties. The syntax for the command is SHOW SPRITE Sprite Number.

The HIDE SPRITE Command

The HIDE SPRITE command makes a specified sprite invisible so that it is not drawn with the other sprites on the screen. The syntax for the command is HIDE SPRITE Sprite Number.

This command might be useful in a game in which you want to add special effects to a sprite without changing its image. For example, suppose you have a space combat game with power-ups. When the player's ship gets a force field power-up, a small outline appears around the ship, showing the player that the power-up is active. Rather than drawing a whole new image for every power-up that the ship can take on, you could draw the force field outline (and any other special effects) as a separate image. Then you simply move the special effects sprites along with the spaceship and make them visible when needed.

The SHOW ALL SPRITES Command

The SHOW ALL SPRITES command universally draws all sprites that were previously set to invisible, setting them all to visible.

The HIDE ALL SPRITES Command

The HIDE ALL SPRITES command universally sets all sprites to invisible, so that they no longer appear on the screen when draw commands are issued.

The All Powerful Game Loop

The key to real-time animation, which is the goal of most games (even turn-based games), is the game loop. The game loop repeats over and over as quickly as possible, and is essentially what has come to be known as the game engine. Granted, there are some powerful 3D game engines out there—often referred to by the games that they were first used to create. Table 11.1 presents a list of game engines in use today.

Table 11.1: Game Engines

Game Engine

Developer

Games That Use It

Wolfenstein 3D

id software

Spear of Destiny

Half-Life

Sierra Studios

Counter-Strike, Opposing Force

Quake III

id software

Quake III Arena, Voyager: Elite Force

Torque

Garage Games

Tribes 2

Doom III

id software

Quake IV

Unreal Warfare

Epic Games

America's Army, Unreal Tournament 2003

Oddly enough, some game engines were created out of existing game engines, so there is a strange hierarchy or family tree of games. For example, Half-Life was based on the Quake engine by id software, and was then used to create several more games, all of which have a family heritage with Quake.

Now, I don't want you to think that all there is to a game engine is a game loop. Obviously, there's a lot more to it than that. Usually a game engine includes 3D special effects such as shadows, lighting, support for level files and character files, multiplayer support, computer-controlled players, game physics, weapon trajectories, and countless other factors. But at the core of all this functionality is a realtime game loop.

Creating a Game Loop

You have already seen a game loop in operation in previous chapters, although I didn't mention it at the time. A game loop is usually set to run indefinitely. When a condition for ending the game has been met, you can use the EXIT command to break out of the loop. To create a game loop, you can use any one of the following looping commands:

The DO loop is probably the most obvious choice because it doesn't use a condition for ending (although you can use the EXIT command to break out of the loop). Figure 11.11 illustrates what usually takes place in a game loop.

Figure 11.11: The usual game loop calls subroutines that handle graphics, sound effects, music, artificial intelligence, user input, and multiplayer functionality.

Using REPEAT…UNTIL for a Game Loop

The REPEAT and WHILE loops are also suitable for a game loop that uses a condition. For example, you could create a variable called Running and create a REPEAT loop, as shown in the following code.

Running = 1 REPEAT IF MOUSECLICK() = 1 Running = 0 ENDIF UNTIL Running <> 1

Using WHILE…ENDWHILE for a Game Loop

The WHILE loop is similar to the REPEAT loop, but the condition is checked at the beginning of the loop rather than at the end.

Running = 1 WHILE Running = 1 IF MOUSECLICK() = 1 Running = 0 ENDIF ENDWHILE

Using DO…LOOP for a Game Loop

The DO loop is my preferred method because there is no condition in the loop statement itself. Instead, I prefer to handle the condition myself, inside the loop.

DO IF MOUSECLICK() = 1 EXIT ENDIF LOOP

Game Timing Commands

The sample game loops I just showed you run as fast as possible with no timing. Although DarkBASIC handles frame rate and screen refresh automatically, it doesn't perform timing, so that is something you have to include in a high-speed game loop yourself. I'll explain some of the timing and screen refresh commands, and then show you a new and improved game loop in the following pages.

The SLEEP and WAIT Commands

The SLEEP command pauses the program for a specified number of milliseconds (where 1,000 milliseconds equal one second). The WAIT command performs the exact same function as SLEEP and is simply an alternative command. Use whichever command is more intuitive to you. For example, you might use SLEEP inside a game loop for short-duration timing, and use the WAIT command elsewhere for longerduration timing (which seems intuitive to me). The syntax of these commands looks like this:

SLEEP Duration WAIT Duration

The TIMER Command

The TIMER command returns the internal system time in milliseconds. The system timer is updated 1,000 times per second, so it is a good way to set the speed of your program to a specific rate (not to be confused with the frame rate). The meaning of speed in this context is the amount of work done by the program every second, such as moving sprites, determining collisions, and so on.

The TIMER command is also an excellent tool for profiling a game. Suppose you have written a high-speed game that has a bottleneck somewhere in the code, but you can't find out what is slowing things down. The best way to find the bottleneck is to check the system time before and after a section of code, and then display the amount of time that the code took to run. Following is an example of timing a section of code.

StartTime = TIMER() 'update the screen SYNC EndTime = TIMER() PRINT "Sync time = "; EndTime - StartTime

Refreshing the Screen

Next to timing, the most important aspect of a game loop is the screen refresh. Regardless of how fast a game loop is running, DarkBASIC can only refresh the screen at a specified frame rate. DarkBASIC tries to keep the screen updated the best that it can, but in a high-speed game loop, you want to take control of the screen update yourself. This involves the SYNC command.

The SYNC Command

As the timing example showed, SYNC is one of the key commands you will use in a game loop, and it is absolutely critical to keep a game running at top speed. If you let DarkBASIC handle the screen refresh for you, it will run much too slowly for a decent game. The SYNC command performs a screen refresh manually; you need to insert this into your game loop after all drawing has been completed. DarkBASIC maintains a hidden copy of the screen called the double buffer, which gets all of your drawing commands. Drawing directly to the screen is too slow, so to speed things up DarkBASIC uses this double buffer for all drawing commands and then quickly copies the double buffer to the screen in one fell swoop. In case you haven't guessed it yet, the SYNC command copies the double buffer to the screen.

The SYNC OFF Command

The SYNC OFF command turns on the automatic screen refresh in DarkBASIC (which is the default), and might be useful if you are at a point in the program where you no longer want to manually perform a screen refresh and you would like DarkBASIC to take over. You can always call SYNC ON again if you need to resume manual screen refresh with the SYNC command.

The SYNC ON Command

To manually use the SYNC command in your game loop, you first need to call SYNC ON to turn off DarkBASIC's automatic screen refresh. After you have called SYNC ON somewhere at the start of the program, it is up to you to call SYNC to update the screen—otherwise, nothing will be displayed and the screen will remain blank.

The SYNC RATE Command

The SYNC RATE command is applicable when DarkBASIC is handling the screen refresh automatically; it sets the frame rate of the display. By default, DarkBASIC tries to maintain a frame rate of 40. However, by using the SYNC RATE command, you can override this default and set the screen refresh to any value. Again, just remember that this is not relevant if you are using SYNC to maintain the screen refresh yourself (which is the preferred method).

The GameLoop Program

To show you how to use a real game loop, I have written a program called GameLoop, which is shown in Figure 11.12. This program is simple, but it does a good job of demonstrating how a REPEAT…UNTIL game loop works.

Figure 11.12: The GameLoop program demonstrates a game loop that displays the game rate.

'--------------------------------- 'Beginner's Guide To DarkBASIC Game Programming 'Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith 'Chapter 11 - GameLoop Program '--------------------------------- 'Initialize the program HIDE MOUSE SYNC ON 'Load the sprite LOAD BITMAP "F15.bmp", 1 GET IMAGE 1,0,0,300,300 'Load background bitmap LOAD BITMAP "background.bmp", 0 'Store part of the background GET IMAGE 2,0,0,200,40 'This is the game loop REPEAT 'draw the F-15 SPRITE 1,170,90,1 'erase the frame rate text PASTE IMAGE 2,0,0 'display the frame rate TEXT 0,0, "Frame rate = " + STR$(SCREEN FPS()) 'update the screen SYNC 'Check for mouse click to exit UNTIL MOUSECLICK()=1 OR ESCAPEKEY()=1 SHOW MOUSE END

Detecting Sprite Collisions

Have you ever wondered how to make a missile blow up an alien ship in a game? Or how about when an enemy plane crashes into yours in a game like 1943 ? Although it seems like a simple feat to check when two sprites crash into each other, the details are actually not so simple. The technical term for this process is collision detection, which means that the position of one sprite is compared to the position of another sprite, and if any of the visible pixels intersect, then the sprites have collided!

Types of Collisions

There are three types of collisions (or lack thereof) for which your program should check.

Okay, technically the third option is not a type of collision, but it is important to consider the no-collision event because it helps to illustrate the other two events (see Figure 11.13).

Figure 11.13: The first example of collision detection shows a complete miss.

Bounding Rectangle Collision

The first real type of collision that I want you to consider is bounding rectangle. Take a look at Figure 11.14, which shows an example of bounding rectangle collision detection.

Figure 11.14: Bounding rectangle collision detection uses the bounding rectangles of two sprites to check for a collision.

As you can see from Figure 11.14, the missile clearly has not hit the fighter plane, and yet bounding rectangle would call this a hit. Why is that helpful, do you suppose? Although this method is not entirely accurate in every case, it is sufficient to narrow down the list of suspect collisions so a more detailed comparison can be made. When there are dozens or even hundreds of sprites in a game, it can be very time consuming to check for a collision between every single one because each sprite must be compared to every other sprite in the game! So, if there are 100 sprites in a game, a collision detection procedure would have to make 10,000 comparisons just to see which sprites have collided! Thankfully, bounding rectangle is a quick way of checking for collisions, so it shouldn't slow down the game. Now on to a more specific method.

Impact Collision Detection

Impact collision detection is a precise method of checking for sprite collisions that involves comparing the actual pixels of each sprite to see whether any sprites overlap (the damage zone). Using this method, you can have a tiny sprite with a relatively large bounding rectangle still return an accurate result (see Figure 11.15).

Figure 11.15: Impact collision (also called pixel-level collision) detection compares the nontransparent pixels of two sprites for overlap.

DarkBASIC provides both types of collision so you can keep the game running at a good speed while still allowing for precision in collision checking (which results in a better game). After a bounding rectangle collision occurs, the program can then perform an impact comparison to see whether it was just a near miss or an actual hit.

Collision Commands

DarkBASIC provides two commands for detecting collisions. (Obviously, you don't need a command to check for no collision!) The two commands are SPRITE COLLISION and SPRITE HIT.

The SPRITE COLLISION Command

The SPRITE COLLISION command uses the bounding rectangle collision detection method to check whether two sprites are overlapping. The syntax is Return Value = SPRITE COLLISION (Sprite Number, Target Sprite Number).

The key word to remember with the SPRITE COLLISION command is overlap. If the bounding boxes of two sprites are overlapping even a little bit, this command will return a 1 (otherwise, it will return a 0).

The SPRITE HIT Command

The SPRITE HIT collision command is more specific and determines whether two sprites have actually hit each other using the more precise impact collision method. This command determines impact by looking at the actual pixels in each sprite and comparing whether any pixels in one sprite are overlapping the pixels in the other. The syntax for this command is Return Value = SPRITE HIT (Sprite Number, Target Sprite Number).

The CollideSprites Program

The CollideSprites program demonstrates how to perform collision detection on a screen filled with sprites. As Figure 11.16 shows, collisions cause the sprites to deflect away from each other. Since there are so few sprites in this demonstration, I have used the SPRITE HIT command that, if you'll recall, uses the impact method.

Figure 11.16: The CollideSprites program demonstrates how to use the sprite collision detection built into DarkBASIC.

'--------------------------------- 'Beginner's Guide To DarkBASIC Game Programming 'Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith 'Chapter 11 - CollideSprites Program '--------------------------------- 'Create some variables MAX = 20 DIM SpriteNum(MAX) DIM SpriteX(MAX) DIM SpriteY(MAX) DIM DirX(MAX) DIM DirY(MAX) 'Initialize the program HIDE MOUSE SYNC ON 'Load the sprite LOAD BITMAP "plane2.bmp", 1 GET IMAGE 1,0,0,50,50 GET IMAGE 2,50,0,100,50 GET IMAGE 3,100,0,150,50 GET IMAGE 4,150,0,200,50 'initialize sprites FOR N = 1 TO MAX SpriteX(N) = RND(540) SpriteY(N) = RND(380) DirX(N) = RND(3) + 1 DirY(N) = RND(3) + 1 SpriteNum(N) = RND(3) + 1 SPRITE N, SpriteX(N), SpriteY(N), SpriteNum(N) NEXT N 'Load background bitmap LOAD BITMAP "background.bmp", 0 'This is the game loop REPEAT IF TIMER() > StartTime + 100 FOR N = 1 TO MAX 'move the sprites SpriteX(N) = SpriteX(N) + DirX(N) IF SpriteX(N) > 540 SpriteX(N) = 540 DirX(N) = DirX(N) * -1 ENDIF IF SpriteX(N) < 1 SpriteX(N) = 1 DirX(N) = DirX(N) * -1 ENDIF SpriteY(N) = SpriteY(N) + DirY(N) IF SpriteY(N) > 380 SpriteY(N) = 380 DirY(N) = DirY(N) * -1 ENDIF IF SpriteY(N) < 1 SpriteY(N) = 1 DirY(N) = DirY(N) * -1 ENDIF 'check for collision IF SPRITE HIT(N,0) > 0 DirX(N) = DirX(N) * -1 DirY(N) = DirY(N) * -1 ENDIF 'draw the sprite SPRITE N,SpriteX(N),SpriteY(N),SpriteNum(N) NEXT N 'update the screen SYNC ENDIF UNTIL MOUSECLICK() = 1 END

Summary

This chapter covered one of the most important subjects in the field of gaming— sprites. Sprites are usually small, animated objects on the screen that give a game its graphics. Although there are many 3D games now, and 3D is the preferred realm for commercial games today, there is still plenty of room for 2D games. Thankfully, DarkBASIC excels at handling sprites, as you have learned in this chapter. In addition to learning how to create and manipulate sprites on the screen, you also learned about transparency and the two methods of determining when sprites collide—bounding rectangle collision and impact collision detection.

Quiz

The chapter quiz will help you retain the information that was covered in this chapter, as well as give you an idea about how well you're doing at understanding the subjects. You can find the answers for this quiz in Appendix A, "Answers to the Chapter Quizzes."

1.

What is a sprite?

  1. A small two-dimensional bitmap drawn on the screen at a specified position and usually with transparency
  2. A small three-dimensional texture drawn at a specified X, Y, Z position with transparency
  3. A large two-dimensional bitmap containing the frames of an animated object that will be drawn on the screen
  4. A small fast-moving mythical character with wings

2.

Which term best describes the process of drawing only solid pixels in a sprite?

  1. Flattery
  2. Animation
  3. Transparency
  4. Collision detection

3.

Which year was Pac-Man released?

  1. 1980
  2. 1963
  3. 1998
  4. 1974

4.

Which classic arcade game featured alien ships with a tractor beam that could capture your ship?

  1. Blasteroids
  2. Galaxian
  3. 1943
  4. Galaga

5.

What was Pac-Man's original name?

  1. Yellow-Man
  2. Puck-Man
  3. Round-Man
  4. Go, Pinky, Go!

6.

Which command performs bounding box collision detection on two sprites?

  1. SPRITE HIT
  2. SPRITE COLLISION
  3. SPRITE OWNS
  4. SPRITE COLLIDED

7.

Which command performs impact collision detection on two sprites?

  1. SPRITE COLLISION
  2. SPRITE COLLIDED
  3. SPRITE HIT
  4. SPRITE MISS

8.

Which command do you use to draw a sprite on the screen (or to the active bitmap)?

  1. DRAW SPRITE
  2. SHOW SPRITE
  3. SPRITE
  4. IMAGE

9.

Which command grabs a portion of an image from a previously loaded bitmap?

  1. GET IMAGE
  2. COPY IMAGE
  3. GRAB IMAGE
  4. TILE IMAGE

10.

What are the three commands that you can use to create a game loop?

  1. REPEAT, WHILE, DOING
  2. DON'T, CONTINUE, WAITING
  3. LOOP, REPEAT, WHILE
  4. DO, REPEAT, WHILE

Answers

1.

A

2.

C

3.

A

4.

D

5.

B

6.

B

7.

C

8.

C

9.

A

10.

D

Категории