PDA

View Full Version : Need help with 2D animation


CarbonX
2003.07.08, 06:06 PM
Hey Everybody,
I am a beginning game programmer looking for help on how to do an animation engine similar to the one found in EV Nova using GWorlds and CopyBits(). I know the C language reasonably well and have a basic understanding of GWorlds. I just can't seem to put the pices together to make an animation with them. Please help. any input is much appreciated:)

Patrick
2003.07.08, 06:09 PM
Hello!

EV:Nova uses a modified version of the SpriteWorld sprite engine for all it's graphics. This engine is freely available and open source, you can check out the latest version's source code and the many examples to get an idea of how it works:


From: Ken Pajala <radiance@istar.ca>
Date: Thu May 29, 2003 5:00:19 PM US/Eastern
To: SpriteWorld Mailing List <swml@SpriteWorld.org>
Subject: SWML: SpriteWorld 3.0 beta 2 available
Reply-To: swml@SpriteWorld.org

Finally :)

Here's the latest SpriteWorld, available in two packages:

http://www.spriteworld.org/bin/SpriteWorld_3.0b2.sitx.bin

And the smaller:

http://www.spriteworld.org/bin/SW_3.0b2_small.sitx.bin

Hopefully everyone is okay with the StuffitX format. It saved almost a meg
over Stuffiit 5 compressing the smaller file alone.

Both archives contain everything you need to get SpriteWorld up and going,
including all the examples and demos. The only difference between the two
is that the smaller one doesn't contain any of the example/demo
applications, while the larger has all of them pre-built for your enjoyment.

For everything you need to know about this release (or at least, everything
I could think of), please read the file "SpriteWorld 3 Beta 2 Notes.htm".

This beta should have a relatively short life span. I was originally going
to call this a final, but then I ended up adding quite a bit to to the beta
1+ code that Anders gave me, so it seemed logical to release another beta.
I'm hoping that any bugs/problems can be found and ironed out within a
couple weeks (well, let's say a month).

Note that the MPW and Project Builder portions are not yet updated. Michael
Matuszek <mmatuszek@earthlink.net> is diligently working on it and we'll
make it available as soon as possible.

Please let me know about any problems or bugs at <guru@spriteworld.org>.
Especially let me know about any issues you have with the initial setup and
compiling of the headers, libraries, code...

Helpful suggestions are always welcome too.

::ken::

P.S. As of last night, color hardware particles now work in scrolling
SpriteWorlds, even though the fix didn't make it into the beta and it's
listed in the "known bugs" file.
_______________________________________________
SWML mailing list
SWML@SpriteWorld.org
http://www.pairlist.net/mailman/listinfo/swml

szymczyk
2003.07.09, 01:35 AM
The actual drawing is pretty easy. You store your animation frames in a GWorld. Call CopyBits() to draw from the GWorld to the back buffer, then a second CopyBits() call draws the animation frame to the screen.

The hard part is organizing your animation frames and figuring out which frame to draw. Organize your animation in a way that's easy for you to understand. Assume you have a human player character who can walk in four directions and has eight frames of movement. A way to organize it is:

Row 1: Player facing up
Row 2: Player facing down
Row 3: Player facing left
Row 4: Player facing right

Columns 1-8 would be the eight frames of movement.

If the player is moving, you increment the column, and move back to the first column when you reach the last frame of the animation. If the player changes direction, you change the row. You use the row and column to determine the rectangle to draw when you call CopyBits() to draw into the back buffer. It will depend on your sprite size and how many frames of animation you have.

That was a simple example. In a real game, you'll want characters to do multiple things like run, jump, and fight, and you'll have to keep track of what the character is currently doing.

If you want a more detailed explanation of sprite animation, read my book Mac Game Programming. You can download source code for the book, which includes sprite animation code, from the publisher's site (www.premierpressbooks.com).

CarbonX
2003.07.09, 04:01 PM
Thanks for the info but, I could use a code example showing how you get the different sprite faces on the screen in sync with the animation. Thanks Again.

Bachus
2003.07.09, 05:11 PM
This isn't all the code you'll need (I don't initialize the drawing buffers for instance), but it should be most of the necessary code. This is gonna be long...

The Rect sizes were meant to be used with these graphics that I "borrowed" from Seiken Densetsu 3. links removed


WindowRef window;

Boolean whichList;
short numUpdateRects1, numUpdateRects2;
Rect updateRects1[kMaxNumUpdateRects], updateRects2[kMaxNumUpdateRects];

GWorldPtr gOffscreenBuffer, gBackgroundBuffer;

tPlayerSpriteType player;
GWorldPtr gPlayerBuffer, gPlayerMasksBuffer;
Rect playerFacesRect, playerMasksRect;
Rect playerRects[6];

#define rPlayerFacesID 128
#define rPlayerMasksID 129

void LoadSprites(void)
{
CGrafPtr curPort;
GDHandle curDevice;
short i;

GetGWorld (&curPort, &curDevice);

// Player Rects
SetRect(&playerFacesRect, 0, 0, 168, 128);
SetRect(&playerMasksRect, 0, 0, 168, 128);

SetRect(&player.idle[kDown].face, 0, 0, 24, 32);
SetRect(&player.idle[kDown].mask, 0, 0, 24, 32);
for (i = 0; i < 6; i++)
{
SetRect(&player.down[i].face, 24+(i*24), 0, 48+(i*24), 32);
SetRect(&player.down[i].mask, 24+(i*24), 0, 48+(i*24), 32);
}

SetRect(&player.idle[kRight].face, 0, 32, 24, 64);
SetRect(&player.idle[kRight].mask, 0, 32, 24, 64);
for (i = 0; i < 6; i++)
{
SetRect(&player.right[i].face, 24+(i*24), 32, 48+(i*24), 64);
SetRect(&player.right[i].mask, 24+(i*24), 32, 48+(i*24), 64);
}

SetRect(&player.idle[kLeft].face, 0, 64, 24, 96);
SetRect(&player.idle[kLeft].mask, 0, 64, 24, 96);
for (i = 0; i < 6; i++)
{
SetRect(&player.left[i].face, 24+(i*24), 64, 48+(i*24), 96);
SetRect(&player.left[i].mask, 24+(i*24), 64, 48+(i*24), 96);
}

SetRect(&player.idle[kUp].face, 0, 96, 64, 128);
SetRect(&player.idle[kUp].mask, 0, 96, 64, 128);
for (i = 0; i < 6; i++)
{
SetRect(&player.up[i].face, 24+(i*24), 96, 48+(i*24), 128);
SetRect(&player.up[i].mask, 24+(i*24), 96, 48+(i*24), 128);
}

gPlayerBuffer = 0L;
CreateOffScreenGWorld(&playerFacesRect, &gPlayerBuffer);
LoadGraphic(rPlayerFacesID);

gPlayerMasksBuffer = 0L;
CreateOffScreenGWorld(&playerMasksRect, &gPlayerMasksBuffer);
LoadGraphic(rPlayerMasksID);

SetGWorld (curPort, curDevice); // Restore the old Port
}

void CreateOffScreenGWorld(Rect *theRect, GWorldPtr *offScreen)
{
NewGWorld(offScreen, 16, theRect, NULL, NULL, keepLocal)
SetPort(*offScreen);
}

void LoadGraphic (short thePictID)
{
Rect bounds;
PicHandle thePicture;

thePicture = GetPicture(thePictID); // Load graphic from resource fork.
HLock((Handle)thePicture); // If we made it this far, lock handle.
bounds = (*thePicture)->picFrame; // Get a copy of the picture's bounds.
HUnlock((Handle)thePicture); // We can unlock the picture now.
OffsetRect(&bounds, -bounds.left, -bounds.top); // Offset bounds rect to (0, 0).
DrawPicture(thePicture, &bounds); // Draw picture to current port.
ReleaseResource((Handle)thePicture); // Dispose of picture from heap.
}

void DrawPlayer (void) // Do the animation and make it appear on the screen.
{
switch (player.mode)
{
case idle:
CopyMask(GetPortBitMapForCopyBits(gPlayerBuffer),
GetPortBitMapForCopyBits(gPlayerMasksBuffer),
GetPortBitMapForCopyBits(gOffscreenBuffer),
&player.idle[player.position.facing].face,
&player.idle[player.position.facing].mask,
&player.position.isAtRect);
break;

case running:
switch (player.position.facing)
{
case kUp:
CopyMask(GetPortBitMapForCopyBits(gPlayerBuffer),
GetPortBitMapForCopyBits(gPlayerMasksBuffer),
GetPortBitMapForCopyBits(gOffscreenBuffer),
&player.up[player.frame].face, &player.up[player.frame].mask,
&player.position.isAtRect);
break;

case kRight:
CopyMask(GetPortBitMapForCopyBits(gPlayerBuffer),
GetPortBitMapForCopyBits(gPlayerMasksBuffer),
GetPortBitMapForCopyBits(gOffscreenBuffer),
&player.right[player.frame].face, &player.right[player.frame].mask,
&player.position.isAtRect);
break;

case kDown:
CopyMask(GetPortBitMapForCopyBits(gPlayerBuffer),
GetPortBitMapForCopyBits(gPlayerMasksBuffer),
GetPortBitMapForCopyBits(gOffscreenBuffer),
&player.down[player.frame].face, &player.down[player.frame].mask,
&player.position.isAtRect);
break;

case kLeft:
CopyMask(GetPortBitMapForCopyBits(gPlayerBuffer),
GetPortBitMapForCopyBits(gPlayerMasksBuffer),
GetPortBitMapForCopyBits(gOffscreenBuffer),
&player.left[player.frame].face, &player.left[player.frame].mask,
&player.position.isAtRect);
break;
}
break;
}
}

// Now we add the player to the update rect list.
AddToUpdateRects(&player.position.isAtRect);

// One of a dozen ways to increase your frame count
player.counter++;
if (player.counter < 24)
player.frame = player.counter / 4;
else
{
player.frame = 0;
player.counter = 0;
}
}

// Stolen from Glypha III
void AddToUpdateRects (Rect *theRect)
{
if (whichList) // We alternate every odd frame between two listsÖ
{ // in order to hold a copy of rects from last frame.
if (numUpdateRects1 < (kMaxNumUpdateRects - 1))
{ // If we are below the maximum # of rects we can handle add the rect to the list (array).
updateRects1[numUpdateRects1] = *theRect;
numUpdateRects1++; // Increment the number of rects held in list.
// Do simple bounds checking (clip to screen).
if (updateRects1[numUpdateRects1].left < 0)
updateRects1[numUpdateRects1].left = 0;
else if (updateRects1[numUpdateRects1].right > 640)
updateRects1[numUpdateRects1].right = 640;

if (updateRects1[numUpdateRects1].top < 0)
updateRects1[numUpdateRects1].top = 0;
else if (updateRects1[numUpdateRects1].bottom > 480)
updateRects1[numUpdateRects1].bottom = 480;
}
}
else // Exactly like the above section, but with the other list.
{
if (numUpdateRects2 < (kMaxNumUpdateRects - 1))
{
updateRects2[numUpdateRects2] = *theRect;
numUpdateRects2++;

if (updateRects2[numUpdateRects2].left < 0)
updateRects2[numUpdateRects2].left = 0;
else if (updateRects2[numUpdateRects2].right > 640)
updateRects2[numUpdateRects2].right = 640;

if (updateRects2[numUpdateRects2].top < 0)
updateRects2[numUpdateRects2].top = 0;
else if (updateRects2[numUpdateRects2].bottom > 480)
updateRects2[numUpdateRects2].bottom = 480;
}
}
}

// Also stolen from Glypha III
void CopyAllRects (void)
{
short i;

if (whichList) // Every other frame, we alternate which list we use.
{ // Copy new graphics to screen.
for (i = 0; i < numUpdateRects1; i++)
{
CopyBits(GetPortBitMapForCopyBits(gOffscreenBuffer ),
GetPortBitMapForCopyBits(GetWindowPort(window)),
&updateRects1[i], &updateRects1[i], srcCopy, (RgnHandle)0L);
}
// Patch up old graphics from last frame.
for (i = 0; i < numUpdateRects2; i++)
{
CopyBits(GetPortBitMapForCopyBits(gOffscreenBuffer ),
GetPortBitMapForCopyBits(GetWindowPort(window)),
&updateRects2[i], &updateRects2[i], srcCopy, (RgnHandle)0L);
}
// Clean up offscreen.
for (i = 0; i < numUpdateRects1; i++)
{
CopyBits(GetPortBitMapForCopyBits(gBackgroundBuffe r),
GetPortBitMapForCopyBits(gOffscreenBuffer),
&updateRects1[i], &updateRects1[i], srcCopy, (RgnHandle)0L);
}

numUpdateRects2 = 0; // Reset number of rects to zero.
whichList = !whichList; // Toggle flag to use other list next frame.
}
else
{ // Copy new graphics to screen.
for (i = 0; i < numUpdateRects2; i++)
{
CopyBits(GetPortBitMapForCopyBits(gOffscreenBuffer ),
GetPortBitMapForCopyBits(GetWindowPort(window)),
&updateRects2[i], &updateRects2[i], srcCopy, (RgnHandle)0L);
}
// Patch up old graphics from last frame.
for (i = 0; i < numUpdateRects1; i++)
{
CopyBits(GetPortBitMapForCopyBits(gOffscreenBuffer ),
GetPortBitMapForCopyBits(GetWindowPort(window)),
&updateRects1[i], &updateRects1[i], srcCopy, (RgnHandle)0L);
}
// Clean up offscreen.
for (i = 0; i < numUpdateRects2; i++)
{
CopyBits(GetPortBitMapForCopyBits(gBackgroundBuffe r),
GetPortBitMapForCopyBits(gOffscreenBuffer),
&updateRects2[i], &updateRects2[i], srcCopy, (RgnHandle)0L);
}

numUpdateRects1 = 0; // Reset number of rects to zero.
whichList = !whichList; // Toggle flag to use other list next frame.
}
}

w_reade
2003.07.09, 07:10 PM
Please don't post links to "borrowed" sprites.

CarbonX
2003.07.10, 02:21 AM
Thanks alot for the code example Im sure it will help me out quite alot. :)

CarbonX
2003.07.10, 04:29 AM
As I was reading over your code example it occured to me that the typedef or struct variable that is used is not included. that snippet would help me better understand what tPlayerSpriteType is. Get back to me as soon as possible on this issue. Thanks:rolleyes:

aarku
2003.07.10, 09:26 AM
This question has been coming up on the SpriteWorld mailing list, so I wanted to ask you and idevgames too. Why do you want to do your own engine? I'll run over the reasons off the top of my head why I suggest to use SpriteWorld:

[list=1]
OpenSource and completely free = flexible (You just have to acknowledge that you use SpriteWorld, see the short license for details.)
Support for RLE (Run Length Encoded) sprites (smaller file sizes, faster loading, faster drawing)
Easily supports hardware-acceleration (OpenGL in X and Rave in 9)
BlitPixie is fast. Faster than CopyBits. Plus put in the option in your game to switch on hardware-acceleration, and you have the potential for major FPS improvements.
Built in time-based animation... so your game won't be choppy
FakeScrolling demo, which shows off the starfield engine and the type of scrolling EV used.
Great loading routines (and loading RLE sprites is VERY quick compared to loading picture-based ones)
Built in support for special effects: translucency, additive, subtractive, rotation, scaling. (Nova... and my game... use a quick additive RLE blitter for things like engine glows and running lights)
Active mailing list for any questions you come up with
[/list=1]

Here is what I can think of why someone wouldn't want to use SpriteWorld:

[list=1]
SpriteWorld's completeness can seem daunting at first. (We are working on easy newbies into SpriteWorld with the upcoming 3.0 final release by providing better introduction documentation.)
Ego issue with using source code that wasn't typed yourself.
Feel that by creating your sprite engine, you'll learn more. (This may be true, but I argue that having a library where you can jump right into and get some animations up on the monitor will keep you going longer; and as you gain familiarity with the library you can pick through the source code to see how things are done, and make any changes you care to. It is very clean code, well maintained and written.)
[/list=1]

-Jon

CarbonX
2003.07.10, 03:48 PM
As you said. I feel that by creating my own animation engine i will be able to better understand how the whole animation process works in C. After I figure it out I will probably download SpriteWorld 3.0 and use that. but until then I need to learn.

-CarbonX

aaronsullivan
2003.07.10, 05:01 PM
Until SpriteWorld X is finished and polished and thoroughly tested, SpriteWorld is only useful for MAC ONLY projects.

Which is okay. It's just that you could have listed that in the reasons to NOT use SpriteWorld...

...Along with more flexibility in implementation. (Features others haven't thought of yet, for instance :))

Also, the free SpriteWorld was ironically NOT supported well on the free ProjectBuilder. You had to use the expensive Codewarrior. (I know some have been working on this)

Plus, if you want to integrate limited 3D models, even SpriteWorld X is likely to be limited.

I used to use SpriteWorld and I really liked it. There will probably be projects in the future that I'll use it for...

Right now, I want to be able to be cross platform so I can share my games with many more people. SpriteWorld doesn't offer this.

Oh, one more thing. People need to DEVELOP engines like SpriteWorld and if everyone only USED the finished engines they would never evolve or improve those engines over time. ;) Honestly I'm not one of those people... so, I take the middle road and use SDL/OpenGL.

In summary, SDL/OpenGL works wonders for me so far. However, there's a bit of engine work you'll have to do yourself to get up to what SpriteWorld already gives you.

Aaron

Patrick
2003.07.10, 05:09 PM
SpriteWorld has worked fine with PB( and MPW) for quite some time, actually.

Bachus
2003.07.10, 06:31 PM
Originally posted by CarbonX
As I was reading over your code example it occured to me that the typedef or struct variable that is used is not included. that snippet would help me better understand what tPlayerSpriteType is. Get back to me as soon as possible on this issue. Thanks:rolleyes:

Ah yes, I forgot to include that.


#define kMaxPlayerFrames 6
#define kUp 0
#define kRight 1
#define kDown 2
#define kLeft 3
typedef enum {idle, running} PlayerStates;

typedef struct
{
Rect isAtRect, wasAtRect; // where the player is/was on the screen
short hVelocity, vVelocity; // horizontal and vertical velocity
short h, v; // horizontal and vertical position of the player
short wasH, wasV;
short facing;
} tSpritePosType;

typedef struct
{
Rect face, mask;
} tPlayerFrames;

typedef struct
{
tPlayerFrames up[kMaxPlayerFrames], right[kMaxPlayerFrames];
tPlayerFrames down[kMaxPlayerFrames], left[kMaxPlayerFrames];
tPlayerFrames idle[4];
tSpritePosType position;
short frame, counter;
PlayerStates mode;
} tPlayerSpriteType;


That should be everything. The code I posted is older code that I no longer use, but it should work well enough. If you want any more sample code along these lines, check out Glypha III (which you can find here in the source code section). It's what I based all my older projects on until I wised up and stopped hardcoding the values for everything.

CarbonX
2003.07.10, 08:11 PM
Thanks alot for the rest of the code. I think it will help me figure out your example alot better now that I have all of the code nesecary. Thanks again

CarbonX
2003.07.12, 03:04 AM
Hi Everybody, I have another question for you all!
I need to know a good way to move my space ship sprite around in the direction it is facing. there is a single pict resource with 36 different angles of the same ship. so my question is How do I get the sprite to move in the exact direction it is facing? without geting all that wierd "sliding" due to poor coding.(sliding in refrence to seeming to move sideways) I have written a function to rotate the sprite right or left but I have not yet written the one to make it accelerate or stop. Please Help!

-CarbonX

Skorche
2003.07.14, 11:11 PM
Am I correct in thinking that you are not yet familiar with trigonometry?

distance in y direction = cos(angle) * distance
distance in x direction = sin(angle) * distance

Very basic trig, you need only to know what angle your sprites are pointed it.

OneSadCookie
2003.07.14, 11:28 PM
Uh, usually in math, x = distance * cos(angle), and y = distance * sin(angle).

In computer graphics, of course, it depends on whether y goes "up" or "down", where you decide angle zero is, and whether you choose to measure angles clockwise or counterclockwise.

CarbonX
2003.07.15, 03:11 AM
Yes you are correct I got shafted in middle school when my 6th grade teacher put me into general math instead of pre algebra. I am now a senior about to take trig. If I had instruction Im sure I could figure it out though.

-CarbonX

davecom
2003.07.15, 04:08 PM
Don't worry, I've taken several years of trig. and am still yet to successfully apply it in my games.

CarbonX
2003.07.16, 02:03 AM
Thanks for all the help everybody!!! Now in order to complete my game that I am creating ( I hope to make it into a tutorial for all of the people who need a little more instruction on making games). Is a good way to create a tiling engine. perhaps a simple one as found in really old school RPGs or a little more complex like the one found in BOOM. I have a book (Tricks of the mac game programming gurus) that roughly outlines how to do this but shows nothing about how to implement it. Thanks for your input.


-Carbon X

Skorche
2003.07.22, 07:42 PM
Gak, OSC you're right. How did I do that?
Horray, my computer is back from warranty repair.

LongJumper
2003.07.23, 02:07 AM
Originally posted by aarku
This question has been coming up on the SpriteWorld mailing list, so I wanted to ask you and idevgames too. Why do you want to do your own engine? I'll run over the reasons off the top of my head why I suggest to use SpriteWorld:


Some people code because they like creating things. Like Prefontaine said, every time he runs a race he is creating something beautiful. Or some such thing.

Not necessarily an ego trip, but say if you wanted to make your wife a coffee table for her birthday, because your wife and you are 40 years old and a 12 pack of corona isn't good anymore. Would you have someone else carve the table, and then you could paint it and say you made a table for her?


But hey, Bill Gates took some code, modified it, and became the richest man in the world. He's also a d-bag, and anyone that has the same mentality as his should get t-bagged, for shizzlebag.

CarbonX
2003.07.25, 03:21 AM
I am a programmer because I love to create things. And because I am not a professional and I am still learning some things I feel no need to use third party engines for doing my games.