I assume you will be using some modern rendering API to draw to the screen, such as OpenGL or D3D. You will certainly want to batch sprites as much as possible and use sprite sheets to reduce the number of textures. A sprite sheet is nothing more than a Texture Atlas (also read this). Once you have a Texture Atlas up and running, it will be up to you how you assign sprite frames/images to them. On common practice is, for example, to use one altas per game character, one for static elements, one for background elements, etc.
To each sprite you can assign a reference to the altas where its frames are and a list of locations inside the altas where the frames are. Instead of atlas locations you can actually store raw texture coordinates (UVs).
OK, so suppose you have a sprite sheet/texture atlas like this one:
You will also need some extra info about the rectangles of each sprite frame (each one of those cats is a sprite frame). You can use anything from a txt
file with (x,y,w,h)
sets on it, from a more complex XML file. But the basic thing you need to specify is the rectangles in image space where the frames are.
So your atlas class would look something like:
class TextureAtlas {
LoadTexture(string filename); // Loads the image itself
LoadMetadata(string filename); // Loads the metadata describing the atlas nodes/sprite frames
Region GetRegion(int index);
Texture texture; // The actual image. Immutable
Region[] nodes; // The nodes/frames. Each is a rectangle inside the 'texture' image
}
Once you have something like that you can define a sprite with:
class SpriteFrame {
// The region inside the atlas texture
// where this frame resides; As texture coords, ready for rendering.
float u0, v0;
float u1, v1;
const TextureAtlas * atlas; // Just a pointer/ref, not the actual object.
// This allows you to share a texture with many sprites.
}
class Sprite {
// And a sprite is just a collection of frames.
SpriteFrame[] frames;
}
Then you would use such setup in much this way:
TextureAtlas catAtlas;
catAtlas.LoadTexture("runningcat.png");
catAtlas.LoadMetadata("runningcat.meta");
Sprite catSprite1 = new Sprite(catAtlas);
Sprite catSprite2 = new Sprite(catAtlas);
...
The constructor of Sprite
still need to go thru every sprite frame and get the UVs from the atlas to set them up. Something like:
for each SpriteFrame frame in Sprite
{
Region region = atlas.GetRegion(i);
frame.u0 = region.x / float(atlas.Width());
frame.v0 = region.y / float(atlas.Height());
frame.u1 = (region.x + frame.w) / float(atlas.Width());
frame.v1 = (region.y + frame.h) / float(atlas.Height());
}
And finally, the rendering would be very simple and efficient:
func DrawSprite(Sprite s)
{
// Apply/bind the atlas texture once
s.atlas.Apply();
for each SpriteFrame frame
{
// Send down the frame texture coords (UVs)
// and geometry vertexes to the rendering API.
// The texture is already set and ready to be used.
}
}