Jump to content
  • Advertisement
Sign in to follow this  
theOcelot

Sprite Animation manager

This topic is 3796 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm trying to write a class that manages, plays, and draws animation sequences from (I think this is the term) a sprite sheet, a large image with the frames. It's in a tile-based game, using SDL. I guess the main thing is exactly how to design the interface. Here's what I have so far. You can just skim it, or don't read it at all if you already know how to do it perfectly [smile]. TileSprite.h
#include <vector>
#include <SDL.h>

enum playmode{ANIM_ONCE, ANIM_LOOP};

class TileSprite{
    protected:
      std::vector< std::vector<int> > sequences;//a vector of vectors of ints
      //inner vectors store animation frame indices
      SDL_Surface* image;
      std::vector< SDL_Rect > srcrects;
      //general stuff
      int curr_sequence;//index into 'sequences'
      int curr_frame;//index into current sequence
      int num_frames;
      playmode curr_mode;//the way the current animation is controlled
      int next_sequence;//sequence to be played indefinitely when current one is finished
      playmode next_mode;
      //function to create all the rects
      int section_rects(int, int);//fills srcects with SDL_Rects for the image, params are dimensions of frames
                   //returns number of frames created
    public:
      TileSprite(SDL_Surface*, int frame_width, int frame_height);
      int add_sequence(int*, int);            //these functions add a sequence of indices
      int add_sequence(std::vector<int>);//to the main vector, returns index seq was placed at
      void update();//updates animation frame
      int draw(SDL_Surface*, int, int);//draws current frame on passed surface, at passed pos.
      void play_sequence(int, playmode);
      void play_two(int, int, playmode);//play first sequence once, then the second one with passed mode
};
TileSprite.cpp(you can pretty much ignore draw(), and the add_sequence functions)
const int default_sequence = 0;
const playmode default_mode = ANIM_LOOP;

TileSprite::TileSprite(SDL_Surface* new_image, int frame_width, int frame_height){
    sequences.reserve(5);//make sure there's room for at least five sequences
    image = new_image;
    section_rects(frame_width, frame_height);
    curr_sequence = 0;
    curr_frame = 0;
    next_sequence = default_sequence;
    next_mode = default_mode;
    curr_mode = ANIM_LOOP;
}

int TileSprite::section_rects(int frame_width, int frame_height){
    //assumes that image is valid
    //it doesn't matter if frame sizes don't go evenly into image size
    //not all the picture will be used
    int num_rects_x = image->w / frame_width;
    int num_rects_y = image->h / frame_height;
    //prepare list
    srcrects.reserve(num_rects_x * num_rects_y);
    //fill it up
    for(int y=0; y<num_rects_y; y++){
        for(int x=0; x<num_rects_x; x++){
            SDL_Rect temp;
            temp.x = x*frame_width;
            temp.y = y*frame_height;
            temp.w = frame_width;
            temp.h = frame_height;
            srcrects.push_back(temp);
        }
    }
    return num_rects_x*num_rects_y;
}

int TileSprite::add_sequence(std::vector<int> new_seq){
    //at some point, bounds checking may be done here
    sequences.push_back(new_seq);
    return sequences.size() - 1;//location that new sequence was placed
}

int TileSprite::add_sequence(int* frames, int n_frames){
    //create vector
    std::vector<int> new_seq;
    new_seq.reserve(n_frames);
    //load it up
    for(int i=0; i<n_frames; i++){
        new_seq.push_back(frames);
    }
    //push it in
    sequences.push_back(new_seq);
    //return location
    return sequences.size() - 1;
}

void TileSprite::update(){
    cout << "TileSprite::update, mode " << curr_mode << endl;
    curr_frame++;//move frame up
    if(sequences[curr_sequence].size() == curr_frame){//basically, if curr_frame has slipped bounds of its sequence
        cout << "  resetting frame" << endl;
        if(curr_mode == ANIM_ONCE){
            cout << "  mode is ANIM_ONCE, going to sequence " << next_sequence << endl;
            curr_sequence = next_sequence;
            curr_mode = next_mode;
            //reset next_*
            next_sequence = default_sequence;
            next_mode = default_mode;
        }
        curr_frame = 0;
    }
}

void TileSprite::play_sequence(int seq, playmode mode){
    if(seq >= sequences.size()){//if sequence is out of bounds...
        //throw Exception("TileSprite::play_sequence: sequence is out of bounds");
    }
    curr_sequence = seq;
    curr_frame = 0;
    curr_mode = mode;
}

void TileSprite::play_two(int new_sequence, int other_sequence, playmode othermode){
    if(new_sequence >= sequences.size() || other_sequence >= sequences.size()){
        //error
    }
    curr_sequence = new_sequence;
    curr_mode = ANIM_ONCE;//only play the first animation once
    next_sequence = other_sequence;
    next_mode = othermode;
}

int TileSprite::draw(SDL_Surface* display, int xpos, int ypos){
    //get destrect
    SDL_Rect destrect;
    destrect.x = xpos;
    destrect.y = ypos;
    destrect.w = destrect.h = 32;
    //get srcrect
    SDL_Rect srcrect(srcrects[ sequences[curr_sequence][curr_frame] ]);
    //draw
    return SDL_BlitSurface(image, &srcrect, display, &destrect);//return value for checking
}
This is all tested and working the way I expect, but I think the design is somewhat flawed. I want to be able to play an animation n times then return to the one before, and also various combinations of "play this animation, then this one forever" or "play this one, then this one n times, then go back to what it was before", I'm sure you get the idea. And I want it all to be in a fairly simple interface, without a ridiculous number of member variables. I figured you guys would have some better ideas on how to design it, and just general tips. I'm especially looking for any wisdom gained from working with a system like this. Thanks!

Share this post


Link to post
Share on other sites
Advertisement
Lines that urk me:

"class TileSprite"
Documentation, please. Is this the class for one tile sprite? Then why does it have so much support for several sprites? Write out in a comment, before the class, EXACTLY what you think this class SHOULD do.

"std::vector< std::vector<int> > sequences;//a vector of vectors of ints"
I can tell it's a vector of vectors of ints by reading "std::vector< std::vector<int> >" Saying what I already know in the comment is redundant. What I don't know is WHY do you have a vect...of ints? The description of that variable should come BEFORE or on the same line as it.

"std::vector< SDL_Rect > srcrects;"
I haven't used SDL for a bit, so this might be more obvious than I realize, but it just looks vague. What's a srcrect?

"int curr_sequence;//index into 'sequences'"
Again, the comment is redundant, the name curr_sequence speaks for itself.

"playmode curr_mode;//the way the current animation is controlled"
This is good documentation in the wrong place. You should move the comment to where you define the enum, playmode.

"f(sequences[curr_sequence].size() == curr_frame){//basically, if curr_frame has slipped bounds of its sequence"
This is good, but what happens if some freak accident causes curr_frame to by greater than sequences[curr_sequence].size()?

Overall, the only thing I don't like about this is it's complicated. If it works, fine. Although, I do see one problem.

Let's say I have a class Monster that has one of these. I have five instances of Monster. How do they all use the same TileSprite, but access it individually?

Here's how I solved this problem in OpenGL:

I have a class called Texture which has a texture and width and height of said texture (in OpenGL, the width and height of a surface (or, in OpenGL, texture) aren't kept with the surface), also support for drawing it. This is no different than what SDL does for you with surfaces.

Then, inspired by the STL, I created a texture container. It stores Texture objects onto an array and can add, remove, or access by index (overloaded [] operator). All it really does is stores the Textures and gives rudimentary access to them.

Then, I created an iterator class, embedded in the container class. Iterators don't contain data, all they do is help you access it. It increments through the container, and I even had it++ return false when it looped back to the beginning.

The container would hold your srcrects and sequences, and the iterator would hold your curr_sequence and curr_frame, etc.

Of coarse, another option is to just add enough support for multiple clients to access the data.

Share this post


Link to post
Share on other sites
Quote:
Original post by Splinter of Chaos
Lines that urk me:

"class TileSprite"
Documentation, please. Is this the class for one tile sprite? Then why does it have so much support for several sprites? Write out in a comment, before the class, EXACTLY what you think this class SHOULD do.


That is a pretty lame name for the class. I have large images, each of can which contain any number of frames for an animation. Each sprite would have one of these. It effectively handles all drawing on the screen for the sprite. It's just that the hardest part of that is animation. Further explanation follows

Quote:

"std::vector< std::vector<int> > sequences;//a vector of vectors of ints"
I can tell it's a vector of vectors of ints by reading "std::vector< std::vector<int> >" Saying what I already know in the comment is redundant. What I don't know is WHY do you have a vect...of ints? The description of that variable should come BEFORE or on the same line as it.


Nested template declarations might be confusing. I guess they're not, really. The ints are indices into 'srcrects'. I had a 'typedef std::vector<int> frame_sequence;', but I figured it wasn't necessary. But it does help make things clearer. I might bring it back
Quote:

"std::vector< SDL_Rect > srcrects;"
I haven't used SDL for a bit, so this might be more obvious than I realize, but it just looks vague. What's a srcrect?


SDL allows you to blit just a portion of an image onto another on, specified by an SDL_Rect. Each one of these 'srcrects' represents one frame of animation.

Quote:

"int curr_sequence;//index into 'sequences'"
Again, the comment is redundant, the name curr_sequence speaks for itself.

"playmode curr_mode;//the way the current animation is controlled"
This is good documentation in the wrong place. You should move the comment to where you define the enum, playmode.


I'm specifying exactly how curr_sequence is used, which may not be immediately obvious to someone else, or even to me later. And there may be more than one instance of playmode. I should doc the declaration though.

Quote:

"f(sequences[curr_sequence].size() == curr_frame){//basically, if curr_frame has slipped bounds of its sequence"
This is good, but what happens if some freak accident causes curr_frame to by greater than sequences[curr_sequence].size()?


Error checking forthcoming if future editions [smile].

Quote:

Overall, the only thing I don't like about this is it's complicated. If it works, fine. Although, I do see one problem.

Let's say I have a class Monster that has one of these. I have five instances of Monster. How do they all use the same TileSprite, but access it individually?


It's complicated because it's designed to support sophisticated behavior.

But I realized that I need to get all the data like srcrects out of each instance. I think I'll fold those into a wrapper for SDL_Surface, and leave the state data like curr_frame in TileSprite, or another class.

Quote:

Here's how I solved this problem in OpenGL:

I have a class called Texture which has a texture and width and height of said texture (in OpenGL, the width and height of a surface (or, in OpenGL, texture) aren't kept with the surface), also support for drawing it. This is no different than what SDL does for you with surfaces.

Then, inspired by the STL, I created a texture container. It stores Texture objects onto an array and can add, remove, or access by index (overloaded [] operator). All it really does is stores the Textures and gives rudimentary access to them.

Then, I created an iterator class, embedded in the container class. Iterators don't contain data, all they do is help you access it. It increments through the container, and I even had it++ return false when it looped back to the beginning.

The container would hold your srcrects and sequences, and the iterator would hold your curr_sequence and curr_frame, etc.

Of coarse, another option is to just add enough support for multiple clients to access the data.


So here, each texture is one frame?

Share this post


Link to post
Share on other sites
I think the point of the criticism was that this class may attempt to do too much. This "play first sequence once, then the second one with passed mode" seems to be going overboard a bit. Wouldn't it be the user of this class that decides what sequence to play? Couldn't complex behaviours such as that be scripted? depend on user input? etc.

I would think that a sprite animation class doesn't need much more beyond being able to play and update an animation, way to report whether the animation has reached the end, and render the frame.

You might have something separate to manage ordering of several animations (play animation once, then play the second animation in loop).

Share this post


Link to post
Share on other sites
Quote:
Original post by visitor
I think the point of the criticism was that this class may attempt to do too much.


Exactly, although, I'm more irked by how many objects are supposed to use the same information. I think the best solution is to have one class that holds the information. One class that holds many instances of the class the holds the information. One class that is the actual animation class. Splitting jobs up simplifies the system and avoids the problem of when you have five sprites who want to use the same information, but animate independently.

A modular approach would also help you with different kinds of animation. The basic container-type supporting event-based (when it's called to update, increment, decrement, whatever, it does so), then an extension for time-based and other types.

Share this post


Link to post
Share on other sites
Quote:
Original post by Splinter of Chaos
Quote:
Original post by visitor
I think the point of the criticism was that this class may attempt to do too much.


Exactly, although, I'm more irked by how many objects are supposed to use the same information. I think the best solution is to have one class that holds the information. One class that holds many instances of the class the holds the information. One class that is the actual animation class. Splitting jobs up simplifies the system and avoids the problem of when you have five sprites who want to use the same information, but animate independently.

A modular approach would also help you with different kinds of animation. The basic container-type supporting event-based (when it's called to update, increment, decrement, whatever, it does so), then an extension for time-based and other types.


I told you I thought the design was flawed! I realized after reading your posts and turning the whole thing over in my mind that it is doing to much. I already said I'm going to split it up.

@visitor
Quote:
This "play first sequence once, then the second one with passed mode" seems to be going overboard a bit. Wouldn't it be the user of this class that decides what sequence to play? Couldn't complex behaviours such as that be scripted? depend on user input? etc.

I would think that a sprite animation class doesn't need much more beyond being able to play and update an animation, way to report whether the animation has reached the end, and render the frame.

You might have something separate to manage ordering of several animations (play animation once, then play the second animation in loop).


The point was to set it up so that playing an animation is a fire-and-forget thing. For example, this play-two function: Say you want a creature to change mode or transform or something. You want it to play a transitional animation, then play it's transformed animation forever, until next change. So like you said, ordering of several animations.

I'm not sure what this is about: "Wouldn't it be the user of this class that decides what sequence to play?". Who did you think was calling the play functions?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!