Advanced SpriteList Techniques

This page provides overviews of advanced techniques. Runnable examples are not guaranteed, as the reader is expected to be able to put the work into implementing them.

Beginners should be careful of the following sections. Some of these techniques can slow down or crash your game if misused.

Draw Order & Sorting

In some cases, you can combine two features of SpriteList:

First, Consider Alternatives

Sorting in Python is a slow, CPU-bound function. Consider the following techniques to eliminate or minimize this cost:

  • Use multiple sprite lists or arcade.Scene to achieve layering

  • Chunk your game world into smaller regions with sprite lists for each, and only sort when something inside moves or changes

  • Use the Sprite.depth attribute with shaders to sort on the GPU

For a conceptual overview of chunks as used in a commercial 2D game, please see the following:

Sorting SpriteLists

Although the alternative listed above are often better, sorting sprite lists to control draw order can still be useful.

Like Python’s built-in list.sort(), you can pass a callable object via the key argument to specify how to sort, along with an optional reverse keyword to reverse the direction of sorting.

Here’s an example of how you could use sorting to quickly create an inefficient prototype:

import random
import arcade


# Warning: the bottom property is extra slow compared to other attributes!
def bottom_edge_as_sort_key(sprite):
    return sprite.bottom


class InefficientTopDownGame(arcade.Window):
    """
    Uses sorting to allow the player to move in front of & behind shrubs

    For non-prototyping purposes, other approaches will be better.
    """

    def __init__(self, num_shrubs=50):
        super().__init__(800, 600, "Inefficient Top-Down Game")

        self.background_color = arcade.color.SAND
        self.shrubs = arcade.SpriteList()
        self.drawable = arcade.SpriteList()

        # Randomly place pale green shrubs around the screen
        for i in range(num_shrubs):
            shrub = arcade.SpriteSolidColor(20, 40, color=arcade.color.BUD_GREEN)
            shrub.position = random.randrange(self.width), random.randrange(self.height)
            self.shrubs.append(shrub)
            self.drawable.append(shrub)

        self.player = arcade.SpriteSolidColor(16, 30, color=arcade.color.RED)
        self.drawable.append(self.player)

    def on_mouse_motion(self, x, y, dx, dy):
        # Update the player position
        self.player.position = x, y
        # Sort the sprites so the highest on the screen draw first
        self.drawable.sort(key=bottom_edge_as_sort_key, reverse=True)

    def on_draw(self):
        self.clear()
        self.drawable.draw()


game = InefficientTopDownGame()
game.run()

Custom Texture Atlases

A TextureAtlas represents Texture data packed side-by-side in video memory. As textures are added, the atlas grows to fit them all into the same portion of your GPU’s memory.

By default, each SpriteList uses the same default atlas. Use the atlas keyword argument to specify a custom atlas for an instance.

This is especially useful to prevent problems when using large or oddly shaped textures.

Please see the following for more information:

Lazy SpriteLists

You can delay creating the OpenGL resources for a SpriteList by passing lazy=True on creation:

sprite_list = SpriteList(lazy=True)

The SpriteList won’t create the OpenGL resources until forced to by one of the following:

  1. The first SpriteList.draw() call on it

  2. SpriteList.initialize()

  3. GPU-backed collisions, if enabled

This behavior is most useful in the following cases:

Case

Primary Purpose

Creating SpriteLists before a Window

CPU-only unit tests which never draw

Parallelized SpriteList creation

Faster loading & world generation via threading or subprocess & pickle

Parallelized Loading

To increase loading speed & reduce stutters during gameplay, you can run pre-gameplay tasks in parallel, such as pre-generating maps or pre-loading assets from disk into RAM.

Warning

Only the main thread is allowed to access OpenGL!

Attempting to access OpenGL from non-main threads will raise an OpenGL Error!

To safely implement parallel loading, you will want to use the following general approach before allowing gameplay to begin:

  1. Pass lazy=True when creating SpriteList instances in your loading code as described above

  2. Sync the SpriteList data back to the main thread or process once loading is finished

  3. Inside the main thread, call Spritelist.initialize() on each sprite list once it’s ready to allocate GPU resources

Very advanced users can use subprocess to create SpriteLists inside another process and the pickle module to help pass data back to the main process.

Please see the following for additional information: