Need help with 2D animation

Moderator
Posts: 102
Joined: 2003.07
Post: #1
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 appreciatedSmile
Patrick
Unregistered
 
Post: #2
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:

Quote: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 Smile

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

http://www.spriteworld.org/bin/SpriteWor...2.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
Member
Posts: 111
Joined: 2002.06
Post: #3
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 (http://www.premierpressbooks.com).

Mark Szymczyk
http://www.meandmark.com
Moderator
Posts: 102
Joined: 2003.07
Post: #4
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.
Member
Posts: 268
Joined: 2005.04
Post: #5
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

Code:
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(gBackgroundBuffer),
                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(gBackgroundBuffer),
                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
Unregistered
 
Post: #6
Please don't post links to "borrowed" sprites.
Moderator
Posts: 102
Joined: 2003.07
Post: #7
Thanks alot for the code example Im sure it will help me out quite alot. Smile
Moderator
Posts: 102
Joined: 2003.07
Post: #8
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. ThanksRolleyes
Moderator
Posts: 522
Joined: 2002.04
Post: #9
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
Moderator
Posts: 102
Joined: 2003.07
Post: #10
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
Member
Posts: 233
Joined: 2003.05
Post: #11
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 Smile)

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. Wink 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

"Pay no attention to that man behind the curtain." - Wizard of Oz
Patrick
Unregistered
 
Post: #12
SpriteWorld has worked fine with PB( and MPW) for quite some time, actually.
Member
Posts: 268
Joined: 2005.04
Post: #13
Quote: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. ThanksRolleyes


Ah yes, I forgot to include that.

Code:
#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.
Moderator
Posts: 102
Joined: 2003.07
Post: #14
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
Moderator
Posts: 102
Joined: 2003.07
Post: #15
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
Thread Closed