Gfx Engine - Sprite Atlas
A sprite atlas is a single image (or texture) that contains multiple smaller images, often referred to as sprites. These smaller images can represent individual assets like characters, objects, UI elements, or animations in a game or application. Sprite atlases are commonly used in 2D game development to improve performance and simplify asset management.
In GFX Engine, a sprite atlas is loaded into memory as a single image. During rendering, texture coordinates of each sprite in the atlas are calculated. These coordinates are used to copy a specific region of the atlas onto the screen.
Use of Sprite atlases gives you the following advantages:
-
Reduced File I/O Operations: Loading many small image files individually can cause multiple I/O operations, which can be slow due to device' internal storage latency. A sprite atlas combines many small images into one, reducing the number of file accesses requred. This leads to faster load times, as fewer files are accessed and loaded into memory.
-
Efficient Memory Usage: Individual images often include extra padding or alignment, wasting memory. A sprite atlas packs multiple images tightly, minimizing wasted space. So the use of memory gets more efficient, which is quite important in resource-constrained systems like WOWCube
-
Faster Rendering: Switching between multiple small images during rendering can involve significant overhead. Using a single texture (sprite atlas) allows the renderer to access all sprites in one memory block, avoiding the need for frequent image switches or caching. So rendering becomes faster and more efficient.
-
Batch Rendering Logic: If each sprite is in a separate image, rendering logic must load and handle each image independently, increasing processing (as well as development) time. With a sprite atlas, the renderer only needs to fetch a specific region of a single image, simplifying logic. So the renderer can efficently draw sprites just by adjusting texture coordinates within the atlas
-
Simpler Asset Management: Managing hundreds of individual image files can be cumbersome. Combining sprites into a single file (atlas) makes managing and updating assets more straightforward and simplifies development workflow, especially for applications with many visual assets.
Sprite atlases provide performance, memory and organizational benefits. They reduce file I/O, streamline rendering logic and improve overall efficiency, making them a common best practice accross various development contexts.
In GFX Engine, we need to take the following steps in order to create and use sprite atlases:
So, in order to creaate and use an offscreen render target whth GFX Engine, we need to do the following:
- Create a
SpriteAtlasobject and register it in the scene - Add sprite objects to it by defining positions and sizes of each sprite
then
- Add the sprite atlas object to the screen inside the
Begin()-End()block in theon_Render()function - Dispose the sprite atlas object when it's not needed anymore to free system resources
Let's go throuhg each step separately.
Step 1 - Initialization
The SpriteAtlas class has just one constructor:
As you can see, to create a sprite atlas you only need two things - the name of the texture file containing the sprite images and a pointer to the Scene object:
-
std::string name
Name of the texture file -
Cubios::Scene scene
Pointer to Scene object
However, if we look at the code in the example, we can see that the constructor of the SpriteAtlas class also requires a template parameter to be specified for the class that is actually responsible for working with the sprite:
This is done for a number of reasons.
Firstly, the SpriteAtlas class is actually just a container for a set of individual sprites (atlas elements), each of which has a separate set of parameters, such as position on the screen, rotation, scaling factor, etc. And the class containing these parameters is, by default, Cubios::SpriteAtlasElement
Secondly, this approach allows you to create a descendant class for a sprite stored in the atlas, in order to be able to add custom parameters to this class. An example of creating an inherited class for a texture atlas element will be discussed below.
Once the SpriteAtlas instance has been created, elements (sprites) need to be added.
The AddSprite method is used for this:
Just like when creating a regular sprite, the first parameter of the atlas sprite element is a unique identifier that allows you to find this sprite in the scene. The second parameter is an object of the Math::Reсt2 class, describing the integer coordinates and size of the sprite image on the texture atlas in pixels.
-
uint32_t id
Sprite resource identifier -
Cubios::Math::Rect2 rc
Sprite texture rectangle
Step 2 - Rendering
The method of drawing sprites-elements of the texture atlas is no different from drawing regular sprites. Sprites are added to the scene inside the Begin-End block.
The only difference is the use of the Get method in the instance of the SpriteAtlas class to obtain a pointer to the required sprite. Otherwise, everything else is the same:
Obviously, the visual objects to add must be created and registered in a scene beforehand.
The Example
Now let's get straight to the example. So, why not make a сubeapp that uses sprite atlases for both the static background and the animated sprites? For example, some fantasy terrain over which fireballs fly?
Easy!
Let's start with the background. In order to draw the background, we use a set of isometric 2D sprites baked into a render target texture. As we know from the previous example, this approach allows us to significantly reduce rendering time, and besides, it is simply more convenient because it gives the opportunity to work with the entire background at once, rather than with a set of background tiles.
The first step, as always, is the initialization of resources in the InitializeResources() function:
Note the last funcion, prepareOffscreenResource(). In that function, the offscreen render target object is created and the composition of tile sprites is done.
And now - a more complex task, animated sprites.
An animated sprite consists of several frames that need to be drawn sequentially in time. This means that we will need a custom class inherited from the basic Cubios::Gfx::SpriteAtlasElement since the base class does not contain properties for working with animations.
Knowing that the animation of each of our fireballs consists of 3 frames, we will create a descendant class that can do the following:
- Move the sprite on the screen at a certain speed (so that the fireballs fly)
- Cyclically select the next frame of the animation with each drawing
- Be able to create own copy. This is necessary in order to be able to draw several instances of the same texture atlas element on the screen
The source code for our custom class will look like this:
Then everything is simple and standard.
First, we initialize another sprite atlas
then we make a call to the function that changes the positions of the sprites in on_Tick() callback
and finally, we render our background and flying fireballs in on_Render()
Servants of darkness, beware!