WOWCube Docs logo
WOWCube Docs
Mission Control
Section Shortcuts
APIExamplesSourceWOWConnectChangelog
Filters
SDK and language defaults persist in cookies.
SDK version
Navigation Tree
Collapsed by default, focused on the active path.
Made byMcKay Seamons
GitHub
  1. Home
  2. Docs
  3. Examples
  4. Gfx Engine - Sprite Atlas
Mission NodeSDK 6.2C++renderingProject Included

Gfx Engine - Sprite Atlas

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 ima...

Examples / SDK 6.2 / C++ / rendering

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:

  1. 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.

  2. 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

  3. 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.

  4. 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

  5. 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 SpriteAtlas object 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 the on_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:

Gfx Engine - Sprite Atlas
CPP
1SpriteAtlas(std::string name, Cubios::Scene* scene)
2
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

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:

Gfx Engine - Sprite Atlas
CPP
1this->terrainAtlas = new Cubios::Gfx::SpriteAtlas<Cubios::Gfx::SpriteAtlasElement>("terrain_atlas.png",&this->Scene);
2
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

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:

Gfx Engine - Sprite Atlas
CPP
1bool AddSprite(uint32_t id, const Cubios::Math::Rect2& rc)
2
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

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:

Gfx Engine - Sprite Atlas
CPP
1//Start adding objects to a screen
2it->Begin(TOPOLOGY_orientation_mode_t::ORIENTATION_MODE_GRAVITY,true);
3
4..
5
6it->Add(this->fireballAtlas->Get(GfxObjects::fireball));
7it->Add(this->fireballAtlas->Get(GfxObjects::fireball+1));
8it->Add(this->fireballAtlas->Get(GfxObjects::fireball+2));
9
10it->Add(this->fireballAtlas->Get(GfxObjects::fireball+3));
11it->Add(this->fireballAtlas->Get(GfxObjects::fireball+4));
12it->Add(this->fireballAtlas->Get(GfxObjects::fireball+5));
13
14//Finish adding objects
15it->End();
16
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

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:

Gfx Engine - Sprite Atlas
CPP
1void GfxSpriteAtlas::InitializeResources()
2{
3 //Create sprite atlas with terrain sprites
4 this->terrainAtlas = new Cubios::Gfx::SpriteAtlas<Cubios::Gfx::SpriteAtlasElement>("terrain_atlas.png",&this->Scene);
5
6 //Define atlas sprites
7 this->terrainAtlas->AddSprite(GfxObjects::terrain, Cubios::Math::Rect2(0,0,163,100));
8 this->terrainAtlas->AddSprite(GfxObjects::pit, Cubios::Math::Rect2(163,0,163,100));
9 this->terrainAtlas->AddSprite(GfxObjects::rock3, Cubios::Math::Rect2(163*2,0,163,100));
10
11 this->terrainAtlas->AddSprite(GfxObjects::rock2, Cubios::Math::Rect2(0,100,163,100));
12 this->terrainAtlas->AddSprite(GfxObjects::rock, Cubios::Math::Rect2(163,100,163,100));
13 this->terrainAtlas->AddSprite(GfxObjects::plant4, Cubios::Math::Rect2(163*2,100,163,100));
14
15 this->terrainAtlas->AddSprite(GfxObjects::plant3, Cubios::Math::Rect2(0,200,163,100));
16 this->terrainAtlas->AddSprite(GfxObjects::plant2, Cubios::Math::Rect2(163,200,163,100));
17 this->terrainAtlas->AddSprite(GfxObjects::plant, Cubios::Math::Rect2(163*2,200,163,100));
18
19 //Create background
20 this->Scene.CreateObjectWithID(GfxObjects::myBackground,new Background(Cubios::Gfx::Colors::black));
21
22 //Create offscreen render target
23 this->prepareOffscreenResource();
24
25 ...
26}
27
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

Note the last funcion, prepareOffscreenResource(). In that function, the offscreen render target object is created and the composition of tile sprites is done.

Gfx Engine - Sprite Atlas
CPP
1void GfxSpriteAtlas::prepareOffscreenResource()
2{
3 OffscreenRenderTarget* rt = new OffscreenRenderTarget(240,240,Cubios::GFX_PixelFormat_t::FORMAT_RGB565);
4 rt->SetPosition(120,120);
5
6 rt->Begin(true);
7
8 rt->Add(this->Scene[GfxObjects::myBackground]);
9
10 rt->Add(terrainAtlas->Get(GfxObjects::terrain))->SetPosition(120,120);
11 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120-79,120-47);
12 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120+79,120-47);
13 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120+79,120+47);
14 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120-79,120+47);
15
16 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120,120-47*2);
17 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120,120+47*2);
18
19 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120-79*2,120-47*2);
20 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120-79*2,120+47*2);
21
22 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120+79*2,120-47*2);
23 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120+79*2,120+47*2);
24
25 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120+79*2,120);
26 rt->AddCopy(terrainAtlas->Get(GfxObjects::terrain)->Copy())->SetPosition(120-79*2,120);
27
28
29 rt->Add(terrainAtlas->Get(GfxObjects::pit))->SetPosition(120,120);
30
31 rt->Add(terrainAtlas->Get(GfxObjects::rock3))->SetPosition(120-79,120-47);
32 rt->Add(terrainAtlas->Get(GfxObjects::rock))->SetPosition(120-79,120-47);
33 rt->Add(terrainAtlas->Get(GfxObjects::rock2))->SetPosition(120,120-47*2);
34
35 rt->AddCopy(terrainAtlas->Get(GfxObjects::rock3)->Copy())->SetPosition(120+79,120-47);
36 rt->AddCopy(terrainAtlas->Get(GfxObjects::plant3)->Copy())->SetPosition(120+79,120-47);
37
38 rt->AddCopy(terrainAtlas->Get(GfxObjects::rock3)->Copy())->SetPosition(120-79,120+47);
39 rt->AddCopy(terrainAtlas->Get(GfxObjects::rock3)->Copy())->SetPosition(120,120+47*2);
40 rt->AddCopy(terrainAtlas->Get(GfxObjects::plant3)->Copy())->SetPosition(120,120+47*2);
41
42 rt->Add(terrainAtlas->Get(GfxObjects::plant))->SetPosition(120+79,120+47);
43 rt->Add(terrainAtlas->Get(GfxObjects::plant4))->SetPosition(120-79,120+77);
44
45 rt->Add(terrainAtlas->Get(GfxObjects::plant2))->SetPosition(120-39,120+7);
46
47 rt->End();
48
49 this->Scene.CreateObjectWithID(GfxObjects::myRT,rt);
50}
51
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

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:

Gfx Engine - Sprite Atlas
CPP
1class FireballSpriteAtlasElement: public Cubios::Gfx::SpriteAtlasElement
2 {
3 public:
4 FireballSpriteAtlasElement(Cubios::Gfx::SpriteAtlasBase* host, const Cubios::Math::Rect2 rc): Cubios::Gfx::SpriteAtlasElement(host,rc)
5 {
6 this->currentFrame = Cubios::random(0,3);
7 }
8
9 virtual ~FireballSpriteAtlasElement() {}
10
11 void Render()
12 {
13 if(this->currentFrame>3) this->currentFrame = 0;
14
15 Cubios::Math::Vec2 pos = this->ScreenPosition();
16 Cubios::GFX_drawSubImage(this->host->resourceId, pos.X,pos.Y,
17 this->rc.v0.X+this->currentFrame*51,this->rc.v0.Y,this->rc.W,this->rc.H,
18 0xFF,
19 Cubios::Gfx::Colors::magenta,
20 Transform.ScaleX, Transform.ScaleY,
21 Transform.SafeRotation()+ScreenAngle, Transform.Mirroring);
22
23 this->currentFrame++;
24 }
25
26 void Tick(int speed)
27 {
28 Transform.Position.X+=speed;
29 if(Transform.Position.X>250) {
30 Transform.Position.X = -20;
31 Transform.Position.Y = Cubios::random(30,180);
32 }
33 }
34
35 FireballSpriteAtlasElement* Copy()
36 {
37 FireballSpriteAtlasElement* obj = new FireballSpriteAtlasElement(this->host, this->rc);
38
39 obj->Transform = this->Transform;
40 obj->Color = this->Color;
41
42 return obj;
43 }
44
45 private:
46 uint8_t currentFrame = 0;
47 };
48
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

Then everything is simple and standard.

First, we initialize another sprite atlas

Gfx Engine - Sprite Atlas
CPP
1void GfxSpriteAtlas::InitializeResources()
2{
3 ...
4
5 //Create an atlas for animated object
6 this->fireballAtlas = new Cubios::Gfx::SpriteAtlas<FireballSpriteAtlasElement>("fireball_atlas.png",&this->Scene);
7
8 //Add dynamic sprite objects
9 this->fireballAtlas->AddSprite(GfxObjects::fireball,Cubios::Math::Rect2(0,0,51,38));
10 this->fireballAtlas->AddSprite(GfxObjects::fireball+1,Cubios::Math::Rect2(0,0,51,38));
11 this->fireballAtlas->AddSprite(GfxObjects::fireball+2,Cubios::Math::Rect2(0,0,51,38));
12
13 this->fireballAtlas->AddSprite(GfxObjects::fireball+3,Cubios::Math::Rect2(0,38,51,20));
14 this->fireballAtlas->AddSprite(GfxObjects::fireball+4,Cubios::Math::Rect2(0,38,51,20));
15 this->fireballAtlas->AddSprite(GfxObjects::fireball+5,Cubios::Math::Rect2(0,38,51,20));
16
17 //Set initial positions
18 this->fireballAtlas->Get(GfxObjects::fireball)->SetPosition(-20,100);
19 this->fireballAtlas->Get(GfxObjects::fireball+1)->SetPosition(-20,80);
20 this->fireballAtlas->Get(GfxObjects::fireball+2)->SetPosition(-20,170);
21
22 this->fireballAtlas->Get(GfxObjects::fireball+3)->SetPosition(-20,70);
23 this->fireballAtlas->Get(GfxObjects::fireball+4)->SetPosition(-20,50);
24 this->fireballAtlas->Get(GfxObjects::fireball+5)->SetPosition(-20,190);
25
26 ...
27}
28
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

then we make a call to the function that changes the positions of the sprites in on_Tick() callback

Gfx Engine - Sprite Atlas
CPP
1void GfxSpriteAtlas::on_Tick(uint32_t currentTime, uint32_t deltaTime)
2{
3 dynamic_cast<FireballSpriteAtlasElement*>(this->fireballAtlas->Get(GfxObjects::fireball))->Tick(15);
4 dynamic_cast<FireballSpriteAtlasElement*>(this->fireballAtlas->Get(GfxObjects::fireball+1))->Tick(10);
5 dynamic_cast<FireballSpriteAtlasElement*>(this->fireballAtlas->Get(GfxObjects::fireball+2))->Tick(12);
6
7 dynamic_cast<FireballSpriteAtlasElement*>(this->fireballAtlas->Get(GfxObjects::fireball+3))->Tick(35);
8 dynamic_cast<FireballSpriteAtlasElement*>(this->fireballAtlas->Get(GfxObjects::fireball+4))->Tick(25);
9 dynamic_cast<FireballSpriteAtlasElement*>(this->fireballAtlas->Get(GfxObjects::fireball+5))->Tick(30);
10
11}
12
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

and finally, we render our background and flying fireballs in on_Render()

Gfx Engine - Sprite Atlas
CPP
1void GfxSpriteAtlas::on_Render(std::array<Cubios::Screen, 3>& screens)
2{
3 for(auto it = screens.begin(); it != screens.end(); ++it)
4 {
5 //Start adding objects to a screen
6 it->Begin(TOPOLOGY_orientation_mode_t::ORIENTATION_MODE_GRAVITY,true);
7
8 it->Add(this->Scene[GfxObjects::myRT]);
9
10 it->Add(this->fireballAtlas->Get(GfxObjects::fireball));
11 it->Add(this->fireballAtlas->Get(GfxObjects::fireball+1));
12 it->Add(this->fireballAtlas->Get(GfxObjects::fireball+2));
13
14 it->Add(this->fireballAtlas->Get(GfxObjects::fireball+3));
15 it->Add(this->fireballAtlas->Get(GfxObjects::fireball+4));
16 it->Add(this->fireballAtlas->Get(GfxObjects::fireball+5));
17
18 //Finish adding objects
19 it->End();
20 }
21}
22
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

Servants of darkness, beware!

Context Rail

Project files

GfxSpriteAtlas.cpp
project/src/GfxSpriteAtlas.cpp
GfxSpriteAtlas.h
project/src/GfxSpriteAtlas.h
wowcubeapp-build.json
project/wowcubeapp-build.json
Jump Grid

On This Page

Gfx Engine Sprite AtlasStep 1 InitializationStep 2 RenderingThe Example
Context Rail

Related nodes

info.json
Examples / SDK 6.2 / C++ / rendering / Gfx Engine - Sprite Atlas
GfxSpriteAtlas.cpp
Examples / SDK 6.2 / C++ / rendering / Gfx Engine - Sprite Atlas / project / src
GfxSpriteAtlas.h
Examples / SDK 6.2 / C++ / rendering / Gfx Engine - Sprite Atlas / project / src
wowcubeapp-build.json
Examples / SDK 6.2 / C++ / rendering / Gfx Engine - Sprite Atlas / project
Previous Node
wowcubeapp-build.json
Examples / SDK 6.2 / C++ / rendering / Gfx Engine - Offscreen Render Targets / project
Next Node
info.json
Examples / SDK 6.2 / C++ / rendering / Gfx Engine - Sprite Atlas