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!