Sprite Sheets with a Uniform Grid
The easiest form of sprite sheets, are sheets that use a uniform grid to lay out the individual sprites of your object. Your sheet is essentially a grid with a certain number of rows and columns of cells. Each cell contains a single sprite.
Using a uniform grid, means that all cells have the exact same dimensions. Additionally, the alignment of the cells is always the same (this means the horizontal and vertical separation between cells is constant).
The image below shows a sheet with a uniform grid (property of pixel artist SmithyGCN)
Required Metadata to Extract Sprites
As you correctly say, you need some metadata that describes the structure of the sprite sheet, in order to extract individual sprites from the sheet. I tend to use the following list of metadata myself (you can use this as an example, and add or remove metadata attributes as needed):
unsigned int m_SpriteWidth; // Width of an individual sprite
unsigned int m_SpriteHeight; // Height of an individual sprite
int m_SpriteOriginX; // Horizontal origin of the sprite
int m_SpriteOriginY; // Vertical origin of the sprite
unsigned int m_SheetWidth; // Width of the sprite sheet image
unsigned int m_SheetHeight; // Height of the sprite sheet image
unsigned int m_SheetColumns; // Number of columns in the sheet
unsigned int m_SheetRows; // Number of rows in the sheet
int m_SheetSeparationX; // Horizontal separation of cells
int m_SheetSeparationY; // Vertical separation of the cells
int m_SheetLeft; // Left position of the first cell
int m_SheetTop; // Top position of the first cell
These parameters will let you use a wide variety of sheets. Here's an explanation and motivation for the chosen parameters:
- The sprite width and sprite height describe how big an individual sprite is. You need this information to generate a quad of the appropriate size to draw on. Additionally, you need this value to calculate the UVs of the rectangular area you want to cut out of your sheet.
- The sprite origin X and sprite origin Y describe where a sprite is drawn when you specify a drawing location. For example, if you set the origin to be at the horizontal center, and vertical bottom of the sprite (14,27), the sprite will be drawn above and centered on the location of the object. This is typically what you want, as a ground-enemy should be drawn "just above" the ground and centered on it's position. Additionally, the origin is used as the center for scaling and rotating.
- The sheet width and sheet height describe the width and height (in pixels) of the complete sprite sheet. This information is needed when calculating the UVs of the rectangular area you want to cut out of the sheet. After all, you need to normalize your UVs to a value between 0.0f and 1.0f, so you'll need to convert your pixel coordinates to relative coordinates through a division by the width and height of the sheet respectively.
- The sheet columns and sheet rows values describe how many rows and columns of cells are within your sprite sheet. You need these to convert a sprite index (= which sprite you want to draw) to pixel coordinates. You'll want to number your sprites in scanline order (i.e. left-to-right, then top-to-bottom). You can then find the column of the sprite as (index % m_SheetColumns) and the row of the sprite as (index / m_SheetColumns). This last calculation works because of the floor-rounding due to integer division. The number of rows can be used to prevent the index from overlowing outside of the sprite sheet range by doing index = (index % (m_SheetColumns * m_SheetRows)) before using it to calculate the column and row.
- The sheet separation X and sheet separation Y values describe the spacing in between cells. In the case of the above sheet, both these values would be 2 (as there is a two pixel white border between cells, that makes up the grid of the sheet). If you tightly pack your individual sprites, this value would be zero. Having a small separation can be useful to make your sheets more clearer to the human eye.
- The sheet left and sheet top values indicate the starting position of the sheet. In the case of the above sheet, a 1 pixel white border was added around the entire sheet. This means that all pixel coordinates need to be shifted one pixel downward and one pixel to the right when calculating the rectangular cutout region. If your top-left cell starts at the very top-left of the sheet image fire (at coordinate (0,0)), this value would be (0,0).
In summary, you can back multiple sprite into one sprite sheet easily by aligning them on a uniform grid. As you said, you will need to metadata that describes the layout of the sheet. The above list contains some suggested attributes that will give you a lot of flexibility in the type of sheets you can employ in your game. Obviously, you can leave out or add new parameters as needed in your use-case.