Resource management and loading screens

Video and audio resource management is a crucial part of game design. Correctly deciding how and when to load sprites, fonts and sound effects can optimize game load times, reduce memory usage and prevent gameplay lag.

Managing graphic resources

Sprites, normal maps, gloss maps, halo and particle bitmaps are all graphic resources that must be loaded from files before being used. By default, Ethanon loads graphic resources on demand, in other words, it automatically prepares the texture from the file only when it needs to draw it on screen (when the entity enters the visible bucket area).

On-demand resource loading is good for its memory friendly characteristics, however, it holds the game update until the texture loading ends, and it might produce undesired gameplay artifacts, which may be avoided by pre-loading some or all resources.

Pre-loading textures

All or some of the graphic resources that are going to be used by the scene can be pre-loaded using the LoadSprite function. Example:

void onSceneCreated()
{
    LoadSprite("entities/boss_entity.png");
    LoadSprite("entities/normalmaps/boss_entity.png");
    LoadSprite("sprites/boss_avatar_icon.png");
}

When this function loads a bitmap that is going to be used by an entity, the engine automatically detects that the sprite has already been loaded and takes a reference to it instead.

Notice that Ethanon doesn't like bitmaps with duplicate names, e.g:

entities/boss.png;
entities/normalmaps/boss.png;
sprites/boss.png,

prefer naming them as

entities/boss_entity.png;
entities/normalmaps/boss_normal.png;
sprites/boss_avatar_icon.png instead.

As in the example above, pre-loading graphic resources inside onSceneCreated function will prevent lag when the level boss appears. Loading every resource used in the level before starting gameplay dynamics is good to minimize lag. But its drawback is that it takes some time to do all that loading, making the player wait.

It is a good practice to pre-load only critical resources that shouldn't lag the game on critical parts of the gameplay.

Animated loading screens

Sometimes pre-loading a huge amount of resources at once is a better way to go, but it will force the player to wait a few seconds until everything is ready. This is unavoidable if we want to assure that the game won't suffer from on-demand loading lag. However, we can make animated loading screens (with or without progress bars) to provide a less-painful waiting experience.

Loading screens can be achieved by creating a scene whose onSceneUpdate callback function loads a small number of resources per frame, while updates each step of the loading animation:

string[] resourcesToLoad;
uint resourceIterator;

void main()
{
    LoadScene("scenes/loading_screen.esc", "prepareLoadingScene", "updateLoadingScene");
}

void prepareLoadingScene()
{
    fill resourcesToLoad array...

    resourceIterator = 0;
}

void updateLoadingScene()
{
    update loading animation, draw progress bar etc ...

    LoadSprite(resourcesToLoad[resourceIterator++]);

    // if it just loaded the last resource in the array, start the game
    if (resourceIterator == resourcesToLoad.length())
        LoadScene("scenes/my_game_scene.esc");
}

With this technique we can take any element of the gameplay concept, and create clever custom loading screens, like in Magic Portals (game powered by Ethanon Engine):

In the example above, the mage walks towards the portal, entering it and vanishing as soon as the last resource is loaded. Easy to make and more enjoyable than a static "Now loading..." message.

Reminder! Persistent resources must be enabled in order to the loading scene work correctly. This feature is explained below.

Managing audio resources

Unlike graphic resources, audio resources aren't automatically loaded on-demand. Instead, it is necessary to pre-load them before using:

void onSceneCreated()
{
    LoadSoundEffect("soundfx/boss_appear.mp3");
    LoadSoundEffect("soundfx/boss_die.mp3");

    LoadMusic("soundfx/boss_soundtrack.mp3");
    PlaySample("soundfx/boss_soundtrack.mp3");
}

On same platforms, like Android, buffered audio resources (buffered means it is not streamed, like sound effects) are loaded on another thread while the game thread is still running normally. So make sure you load all audio resources at least three seconds before using them. Trying to play unfinished sound samples will cause the playback to fail.

Persistent resources

By default, every time a scene load request takes place, all graphic and buffered audio resources are destroyed. This is memory-friendly but, if the next scene uses most of the resources that were already loaded for the previous scene (e.g.: going from forest_level_1.esc to forest_level_2.esc), it will result in unnecessary CPU work and therefore unnecessary waiting time. This can be solved by enabling Persistent Resources:

void main()
{
    SetPersistentResources(true);
}

Enabling persistent resources will cause the scene to keep graphic and buffered audio resources even when another scene is loaded. It will drastically reduce load times, on the other hand, it may also increase memory usage by keeping resources that are no longer used.

Releasing resources

When using persistent resources, the developer is responsible for keeping the game from using too much system memory. For example, all forest scenarios will probably use a set of "green" assets for entities that are going to be used on most forest scenarios, these can persist as long as we're on the "forest world". But will no longer be needed as the player enters the "castle world", which will use unique tile assets.

The ReleaseResources function can be used to clear all loaded resources when necessary:

// releases current resources
ReleaseResources();

This function should be placed on strategic places when a huge amount of new resources needs to be loaded and another set of resources will no loger be necessary.

onResume callback function

On some platforms, like Android, every time the game window is hidden (e.g. the user presses the Home button or receives a call), it won't finish the application. Instead, it will simply pause it, unless the operating system needs to free some memory in order to run another application. Not closing the game application allows the user to resume the game from exactly the same point it had been interrupted.

Some times, when the game is paused by some external force, its window loses its graphic context and, for safety reasons, it releases all graphic and audio resources loaded so far. And when the game is resumed, Ethanon Engine runs a special type of scene callback function called onResume, where all resources must be pre-loaded.

As well as onSceneCreated and onSceneUpdate callback functions, the onResume function can also be set as a LoadScene argument:

void main()
{
    LoadScene("scenes/castle.esc", "onSceneCreated", "onSceneUpdate", "onResume");
}

void onResume()
{
    // reload all audio and graphic resources here
}

...
...

When graphic resources aren't pre-loaded inside the onResume function, Ethanon will load them on-demand, however, since audio resources aren't loaded on-demand, they must be loaded again.