MacTech Network:   MacForge.net  |  Computer Memory  |  Register Domains  |  Printer Supplies  |  Cables  |  iPod Deals  |  Mac Deals  |  Mac Book Shelf


  MacTech Magazine

The journal of Macintosh technology

 
 
Surround SCM 5

Magazine In Print
  About MacTech  
  Home Page  
  Subscribe  
  Archives DVD  
  Submit News  
  Submit a Tip!  
  Get a copy of MacTech RISK FREE  
Google
Entire Web
mactech.com
Mac Community
More...
MacTech Central
  by Category  
  by Company  
  by Product  
MacTech News
  MacTech News  
  Previous News  
  MacTech RSS  
Article Archives
  Show Indices  
  by Volume  
  by Author  
  Source Code FTP  
Inside MacTech
  Writer's Kit  
  Editorial Staff  
  Editorial Calendar  
  Back Issues  
  Advertising  
Contact Us
  Customer Service  
  MacTech Store  
  Legal/Disclaimers  
  Webmaster Feedback  
ADVERTISEMENT
Click Here

Volume Number: 14
Issue Number: 7
Column Tag: Tools Of The Trade

A Tour of SpriteWorld

by by Stefan Sinclair
Edited by Peter N Lewis

A Review of the SpriteWorld Animation Library

Most of today's computer games fall into one of two categories, based on their graphics model: games with two dimensional, scrolling graphics, or those built on a three dimensional graphics system. While the growing power of home computer systems has allowed an explosion of new 3-D based products in recent years, 2-D games such as Command & Conquer and WarCraft remain a popular segment of the computer gaming market. A large percentage of these 2-D games employ what is known as "sprite animation" - the use of graphic objects consisting of a number of pixel-map images shown in rapid succession to simulate movement on the screen.

To accomplish sprite animation on the Macintosh, you can either develop your own sprite animation code from scratch (which can be a time consuming endeavor) or make use of one of a number of existing Mac OS sprite animation libraries available to the Mac OS developer. Popular sprite animation libraries include SpriteWorld, the Sprite Animation Toolkit (SAT), and Apple's Game Sprockets (DrawSprocket in particular). This article will introduce one such library, SpriteWorld, and show you how to use it in your applications.

What is SpriteWorld?

SpriteWorld was originally developed in the early 1990s by Tony Myles as a library of C routines and special pixel blitters (custom high speed drawing routines) to implement fast, smooth sprite animation on the Macintosh. In 1996, Karl Bunker and Vern Jensen added a number of their own enhancements to the original SpriteWorld, including the ability to have a scrolling animation, tiled backgrounds and many new 680x0 and PowerPC blitters and released the new package as SpriteWorld 2.0. Shortly thereafter, a Pascal interface to SpriteWorld was released, as well as a C++/object-based version of SpriteWorld. There is even a version that builds under X-Windows and Win95/NT systems. SpriteWorld remains actively supported; at the time of this writing a new version (2.1) is undergoing beta testing. The source code remains freely available, and comes complete with API documentation as well as several sample programs. The current version of SpriteWorld requires a Macintosh with Color QuickDraw and System 7 or higher.

The SpriteWorld API is built around four basic data structures: SpriteWorlds, SpriteLayers, Sprites, and Frames. The SpriteWorld is at the top of the hierarchy, driving the animation and providing the means with which to display the animation in a graphics device (either on or off-screen). SpriteLayers are an organizational data structure used to group Sprites. Sprites are the graphic objects that move about and animate on screen. Frames are the individual animation frames contained by Sprites, consisting of a pixel map and a mask image. An application may contain any number of SpriteWorlds, a SpriteWorld may contain any number of SpriteLayers, a SpriteLayer may contain any number of Sprites, and a Sprite may contain any number of Frames. For the remainder of the article, the words "SpriteWorld", "SpriteLayer", "Sprite" and "Frame" will be capitalized when referring to the actual SpriteWorld data structures for clarity.

SpriteWorlds

SpriteWorlds are the master data structures used to control the animation. A SpriteWorld contains three Frames used internally for drawing; a background Frame, a work-area Frame, and a window Frame into which the final animation image is displayed. A SpriteWorld also contains a list of the SpriteLayers that it holds. SpriteWorlds which employ a tiled background (a background comprised of several smaller, repeating images which can be animated themselves) will contain a tile map data structure. SpriteWorlds which employ scrolling animation (animation in which the visible area is smaller than the total area of the animated world) will maintain additional data structures to track the movement of the viewable area of animation with respect to the total animated world.

SpriteWorlds control the erasing and drawing of sprites as they move about and iterate through their animation frames, updating the data structures of its SpriteLayers and their Sprites accordingly. They can do so at regular time intervals if the programmer requests that a specific animation frame rate be used via a call to the function SWSetSpriteWorldMaxFPS(), or the SpriteWorld can be left to animate as fast as the hardware will allow. By default, a SpriteWorld uses CopyBits() for all drawing, but a number of custom blitters are included with the SpriteWorld package and are easily implemented for increased speed if desired. To aid in the creation of SpriteWorlds, there are a number of routines available to the programmer; for instance, to create a SpriteWorld from an existing window, SWCreateSpriteWorldFromWindow() can be used.

SpriteLayers

SpriteLayers are essentially nodes in a linked list used to provide a method of sorting Sprites, very similar in concept to the layering schemes found in higher-end graphics programs. A SpriteWorld will have one or more SpriteLayers, and each layer can contain any number of Sprites. The ordering of SpriteLayers determines the order in which drawing occurs; the bottom layer is drawn first, and the top layer is drawn last. In addition, Sprites within individual layers are drawn in the order which they have been added to that layer. Therefore, a Sprite on one layer can be covered by Sprites on higher layers as well as Sprites on the same layer which were added after that Sprite, providing a sense of depth.

As an example, refer to the following illustrations which depict a SpriteWorld containing four SpriteLayers. A scene has been painted in the SpriteWorld's background Frame, which is drawn first. Then the sprites on each layer are drawn, from the bottom layer (containing the car sprite) to the top layer (containing one of the character sprites). Note that the same visual result could also have been realized by instead using a single SpriteLayer with the car sprite added first followed by the three character sprites.

Figure 1. Sprite Layer Conceptual Illustration.

Figure 2. Animation As Displayed On Screen.

Finally, SpriteLayers are also the basis for the collision detection methods in SpriteWorld. When one wants to check for a collision between Sprites in SpriteWorld, the collision detection is done against two layers (or one layer against itself to check for collisions between Sprites on the same layer) using the SWCollideSpriteLayer() routine. As an example, one could have two SpriteLayers, one for enemy space ships (call it enemyLayerPtr) and one for friendly space ships (call it friendlyLayerPtr). To check if any enemy space ships have collided with friendly ships, the code used would be:

SWCollideSpriteLayer(enemyLayerPtr, friendlyLayerPtr);

To take this a step further, one could check to see if any friendly ships had collided with each other:

SWCollideSpriteLayer(friendlyLayerPtr, friendlyLayerPtr);

If a collision is detected between Sprites, each Sprite involved in the collision will have its collision call-back routine called (if it has one), which will determine how the Sprite reacts to the collision. If a Sprite has not had a collision call-back routine installed, it will simply pass through other Sprites regardless of any collisions which take place.

Sprites

Sprites are the workhorses in SpriteWorld. In addition to containing the image information used to display the animation graphics on screen in the form of an array of Sprite Frames, a Sprite contains data used to define its position in the SpriteWorld, its velocity, the frame rate of its animation, and other internally used information. Sprites may also contain a number of callback routines used to control such things as the Sprite's movement, response to collision, behavior when there is to be a change in the currently drawn animation frame, and the implementation of custom blitters for drawing the Sprite.

A number of routines are available to the programmer to create Sprites from standard Macintosh resource types, namely 'PICT' and 'cicn' resources. These include:

  • SWCreateSpriteFromCicnResource() which will create a Sprite from a series of 'cicn' resources to be used as the sprite Frames.
  • SWCreateSpriteFromPictResource() which will create a Sprite from a series of 'PICT' resources to be used as the sprite Frames.
  • SWCreateSpriteFromSinglePict() which will create a Sprite from a single 'PICT' resource, which contains a number of images, either varying in size and location within the 'PICT' or of identical size arranged in a horizontal or vertical strip, to be used as the sprite Frames.
  • SWCreateSpriteFromSinglePictXY() which will create a Sprite from a single 'PICT' resource, which contains a number of images arranged in rows and columns to be used as the sprite Frames.

On 68k Macintosh systems which will be displaying a SpriteWorld animation in 8-bit depth, the programmer has the additional option of using compiled Sprites for maximum possible speed. Using compiled Sprites causes the Sprite to be drawn off screen with binary code generated specifically for that Sprite's pixel map images.

Frames

Frames are data structures that store the actual image data used in the animation. They consist primarily of a pixel image stored in a GWorld and a mask used when drawing the pixel image. Think of frames as being similar to the individual frames on a reel of film, which when shown in rapid succession one after another provide the illusion of a smooth display.

Programming with SpriteWorld

There are typically six steps to creating and running a SpriteWorld program:

  • Initialization of the SpriteWorld package with a call to SWEnterSpriteWorld().
  • Create the components of your animation - SpriteWorld(s), SpriteLayers, Sprites and the Sprite Frames.
  • Assemble the various components - add the Frames to the Sprites, add the Sprites to the appropriate SpriteLayers, add the SpriteLayers to the appropriate SpriteWorlds.
  • Assign the desired custom movement, collision, drawing and frame advancement procedures to the Sprites in order to define their behavior.
  • Run the animation with repeated calls to the SWProcessSpriteWorld() and SWAnimateSpriteWorld() functions in a tight loop, optionally performing any desired collision detection. These two SpriteWorld functions will handle all of the drawing and call any installed movement, collision and frame advancing procedures installed in the Sprites automatically.
  • When finished, dispose of any SpriteWorld data created for the animation.

Now that a general overview of the SpriteWorld library has been presented, a sample program is in order. The following program (which is a trimmed down version of one of the SpriteWorld sample programs by Vern Jensen) creates a SpriteWorld from the application's window, and makes use of both the tiling and scrolling features to provide a large "virtual" world within which the main Sprite (a blue ball) will move about. It creates the main Sprite from a sequence of 'cicn' resources, and adds a movement routine to the Sprite, which allows the Sprite's motion to be controlled via keyboard input. A little bit of extra code was added to allow collisions between the Sprite and certain tiles (representing walls) to be detected. The result is a program that allows the user to control the Sprite with the keyboard, moving it about in the virtual world and causing it to jump across platforms. Note that the initialization of the Macintosh ToolBox is taken care of using a special SpriteWorld utility function, Initialize().

Listing 1. SW_Demo.c

// SW_Demo.c
// include some required SpriteWorld header files
#include "SWIncludes.h"

// include a demo-specific header
#include "SW_Demo.h"

// main
void main(void)
{
	Initialize(0);
	AllowKeyUpEvents();

	CreateSpriteWorld();
	CreateSprites();

	SetUpAnimation();

	AnimationLoop();

	ShutDown();

	RestoreEventMask();
}

In the function CreateSpriteWorld(), we perform the first step described earlier, initialization of the SpriteWorld package with a call to SWEnterSpriteWorld(), and part of the second step with the creation of the SpriteWorld. The SpriteWorld created will incorporate both a scrolling display and a tiled background. The tile map used for the background in this example is constructed using a predetermined algorithm to provide a boundary of "wall" tiles, "step" tiles and "sky" tiles.

Listing 2. CreateSpriteWorld()

// CreateSpriteWorld
void CreateSpriteWorld(void)
{
	Rect	offscreenRect, worldRect, windRect;
	OSErr	err;
	short	row, col;

	gWindowP = GetNewCWindow(kWindowResID, NULL,
		(WindowPtr)-1L);

	ShowWindow(gWindowP);
	SetPort(gWindowP);

	err = SWEnterSpriteWorld();

	worldRect = gWindowP->portRect;
	InsetRect(&worldRect, kWorldRectInset, kWorldRectInset);

	// Set size of offscreen area
	offscreenRect = worldRect;
	OffsetRect(&offscreenRect, -offscreenRect.left,
		-offscreenRect.top);

	// Create the scrolling sprite world
	err = SWCreateSpriteWorldFromWindow(&gSpriteWorldP,
		(CWindowPtr)gWindowP, &worldRect, &offscreenRect);

	// Initialize the tiling features of SpriteWorld,
	// since this program is going to use them
	err = SWInitTiling(gSpriteWorldP, kTileHeight,
		kTileWidth, kMaxNumTiles);
	// Create the tile map
	err = SWCreateTileMap(gSpriteWorldP, &gTileMap,
		kTileMapRows, kTileMapCols);

	// Load first set of tiles
	err = SWLoadTilesFromPictResource(
		gSpriteWorldP,
		kWallTile,	// startTileID
		kSkyTile,	// endTileID
		200,				// pictResID
		0,					// maskResID
		kNoMask,		// maskType
		1,					// horizBorderWidth
		1);				// vertBorderHeight

	// Set up the tileMap according to a predetermined
	// scheme, with the tile map consisting of
	// "sky" tiles and "wall" tiles
	for (row = 0; row < kTileMapRows; row++)
	{
		for (col = 0; col < kTileMapCols; col++)
		{
			if (row <= 4 || col <= 3 ||
				row >= kTileMapRows-5 ||
				col >= kTileMapCols-4)
			{
				gTileMap[row][col] = kWallTile;
			}
			else
			{
				gTileMap[row][col] = kSkyTile;
			}
		}
	}

	// Draw the platforms
	for (row = kTileMapRows-7, col = 6; row > 5 &&
		col < kTileMapCols-8; row--, col += 4)
	{
		gTileMap[row][col] = kWallTile;
		gTileMap[row][col+1] = kWallTile;
	}
}

In the function CreateSprites(), we finish the second step with the creation of the Sprites (and at the same time their Frames), and then move on to the third step by adding the Sprites to the SpriteLayer, and the SpriteLayer to the SpriteWorld. The SpriteWorld is also "locked" with a call to SWLockSpriteWorld(). Locking a SpriteWorld is necessary before animation begins, as it locks the many Handles used to keep their memory from moving during animation.

Listing 3. CreateSprites()

// CreateSprites
void CreateSprites(void)
{
	SpriteLayerPtr	spriteLayerP;
	OSErr			err;

	// Create the sprite layer
	err = SWCreateSpriteLayer(&spriteLayerP);
	FatalError(err);

	// Create the main sprite from 'cicn' resources
	err = SWCreateSpriteFromCicnResource(gSpriteWorldP,
		&gSimpleSpriteP, NULL, 129, 1, kRegionMask);
	FatalError(err);

	// Set up the main sprite
	SWSetSpriteMoveProc(gSimpleSpriteP, KeySpriteMoveProc);
	SWSetSpriteLocation(gSimpleSpriteP,
		kStartCol * kTileWidth,
		kStartRow * kTileHeight);
	SWSetSpriteMoveDelta(gSimpleSpriteP, 0, 0);

	// Add the sprite to the sprite layer
	SWAddSprite(spriteLayerP, gSimpleSpriteP);
	// Add the sprite layer to the sprite world
	SWAddSpriteLayer(gSpriteWorldP, spriteLayerP);
	// Lock the sprite world
	SWLockSpriteWorld(gSpriteWorldP);
}

The function SetUpAnimation() is used to perform the fourth step in creating our animation, which is the assignment of the special procedures to the Sprite(s) which will determine their behavior, and setting up some additional parameters for the SpriteWorld such as maximum animation speed and a special procedure to control the scrolling movement of the display during animation. Once the animation is set up, we draw the tiles in the background of the SpriteWorld and force the screen display to update with calls to SWDrawTilesInBackground() and SWUpdateScrollingSpriteWorld() respectively.

Listing 4. SetUpAnimation()

// SetUpAnimation
void SetUpAnimation(void)
{
	Rect		moveBoundsRect;

	// Place an upper limit on the animation speed
	SWSetSpriteWorldMaxFPS(gSpriteWorldP, kMaxFPS);

	// Set the sprite's movement boundary
	// movement boundary = size of tileMap
	SetRect(&moveBoundsRect, 0,0, kTileMapCols * kTileWidth,
		kTileMapRows * kTileHeight);

	SWSetScrollingWorldMoveBounds(gSpriteWorldP,
		&moveBoundsRect);
	SWSetScrollingWorldMoveProc(gSpriteWorldP,
		SmoothScrollingWorldMoveProc, gSimpleSpriteP);

	// Move the visible scrolling view to starting
	// sprite position
	SWMoveVisScrollRect(gSpriteWorldP,
		gSpriteWorldP->followSpriteP->destFrameRect.left -
		gSpriteWorldP->backRect.right/2,
		gSpriteWorldP->followSpriteP->destFrameRect.top -
		gSpriteWorldP->backRect.bottom/2);

	ForeColor(blackColor);
	BackColor(whiteColor);

	SWDrawTilesInBackground(gSpriteWorldP);
	SWUpdateScrollingSpriteWorld(gSpriteWorldP, true);
}

With the function AnimationLoop() the fifth step in our SpriteWorld animation is achieved, which is to drive the animation in a tight loop. The animation in this example has been setup so as to run until the mouse button is clicked.

Listing 5. AnimationLoop()

// AnimationLoop
void AnimationLoop(void)
{
	// The animation will run until the mouse is clicked
	while (!Button())
	{
		SWProcessScrollingSpriteWorld(gSpriteWorldP);
		SWAnimateScrollingSpriteWorld(gSpriteWorldP);
	}
}

The last step in a SpriteWorld program is to dispose of the objects we have created and ask the SpriteWorld engine to clean up after itself. This is done within the ShutDown() routine of our sample program, which unlocks the SpriteWorld with a call to SWUnlockSpriteWorld() (matching the earlier call to SWLockSpriteWorld()), frees all memory used with a call to SWDisposeSpriteWorld() and finishes cleaning up with a call to SWExitSpriteWorld() (matching the earlier call to SWEnterSpriteWorld()).

Listing 6. ShutDown()

// ShutDown (clean up and dispose of the SpriteWorld)
void ShutDown(void)
{
	SWUnlockSpriteWorld(gSpriteWorldP);
	SWDisposeSpriteWorld(gSpriteWorldP);
	SWExitSpriteWorld();

	FlushEvents(everyEvent, 0);
}

Figure 3. Demo Program Screen.

Conclusion

Using SpriteWorld, the developer is able to build applications using sprite animation quickly and easily without needing to worry about the low level implementation of the animation engine itself. Animation can be a simple "set up and forget" endeavor, or they can be quite complex, with several sprites whose behavior changes dynamically. The excellent documentation and abundant sample code make it easy for the beginner to get started with SpriteWorld, while the availability of the source code lets the more advanced user dig into the API and expand upon it if need be.

To download the SpriteWorld package, you can visit the SpriteWorld web page at http://members.aol.com/SpriteWld2/ or the corresponding SpriteWorld FTP site at ftp://members.aol.com/SpriteWld2/. SpriteWorld is also available from any of the Info-Mac mirrors. There is also a SpriteWorld mailing list; instructions for subscribing to the SpriteWorld mailing list can be obtained on the Web at http://www.us.itd.umich.edu/~hinoue/mailinglist.html.


Stefan Sinclair is currently employed as a mechanical engineer in the cellular and land mobile communications industry where he designs antennas and connector systems. He has been programming as a hobbyist for a number of years under the Mac OS as well as Unix/x-Windows. You can contact him via e-mail at trickys@doomhammer.com.



Click here to find out more about our best subscription bundle deal ever!
2 years of the magazine, and the all new MacTech DVD ... at 70% off!



Click on the cover to
see this month's issue!

TRIAL SUBSCRIPTION
Get a RISK-FREE subscription to the only technical Mac magazine!
 
 


MacTech Magazine. www.mactech.com
Toll Free 877-MACTECH, Outside US/Canada: 805-494-9797

Register Low Cost (ok dirt cheap!) Domain Names in the MacTech Domain Store. As low as $1.99!
Save on brand compatible and name brank ink jet and laser supplies.
Save on long distance * Upgrade your Computer
Movies with No Late Fees!

See local info about Westlake Village
SJ * BRJ * BJ * OJ * NITS
Staff Site Links



All contents are Copyright 1984-2007 by Xplain Corporation. All rights reserved.

MacTech is a registered trademark of Xplain Corporation. Xplain, Video Depot, Movie Depot, Palm OS Depot, Explain It, MacDev, MacDev-1, THINK Reference, NetProfessional, NetProLive, JavaTech, WebTech, BeTech, LinuxTech, Apple Expo, MacTech Central and the MacTutorMan are trademarks or service marks of Xplain Corporation. Sprocket is a registered trademark of eSprocket Corporation. Other trademarks and copyrights appearing in this printing or software remain the property of their respective holders.