Skip to content

Sprite

Low-level bitmap descriptor and multi-layer composition for retro rendering.

Description

Sprites are the fundamental graphics primitive in PixelRoot32. The engine supports multiple sprite formats:

  • 1bpp (Standard): Monochrome sprites, most memory-efficient
  • 2bpp (Experimental): 4 colors per sprite
  • 4bpp (Experimental): 16 colors per sprite
  • MultiSprite: Multi-layer 1bpp sprites for multi-color effects

Namespace

namespace pixelroot32::graphics {
    struct Sprite {
        // ...
    };

    struct MultiSprite {
        // ...
    };

    struct SpriteLayer {
        // ...
    };
}

Sprite Structure (1bpp)

Compact sprite descriptor for monochrome bitmapped sprites.

Members

  • const uint16_t* data: Pointer to packed row data (size = height)
  • uint8_t width: Sprite width in pixels (≤ 16)
  • uint8_t height: Sprite height in pixels

Data Format

Sprites are stored as an array of 16-bit rows. Each row packs horizontal pixels into bits:

  • Bit 0: Leftmost pixel of the row
  • Bit (width - 1): Rightmost pixel of the row
  • Bit value 1: Pixel on (colored)
  • Bit value 0: Pixel off (transparent/background)

Only the lowest width bits of each row are used.

Example

// 8x8 sprite (smiley face)
static const uint16_t SMILEY_DATA[] = {
    0b00111100,  // Row 0:  ████
    0b01111110,  // Row 1:  ██████
    0b11011011,  // Row 2:  ██ ██ ██
    0b11111111,  // Row 3:  ████████
    0b11011011,  // Row 4:  ██ ██ ██
    0b01100110,  // Row 5:  ██  ██
    0b01111110,  // Row 6:  ██████
    0b00111100   // Row 7:  ████
};

static const Sprite SMILEY_SPRITE = {
    SMILEY_DATA,
    8,  // width
    8   // height
};

SpriteLayer Structure

Single monochrome layer used by layered sprites.

Members

  • const uint16_t* data: Pointer to packed row data for this layer
  • Color color: Color used for "on" pixels in this layer

Notes

  • Each layer uses the same width/height as its owning MultiSprite
  • Layers can have different colors
  • Layers are drawn in array order

MultiSprite Structure

Multi-layer, multi-color sprite built from 1bpp layers.

Members

  • uint8_t width: Sprite width in pixels (≤ 16)
  • uint8_t height: Sprite height in pixels
  • const SpriteLayer* layers: Pointer to array of layers
  • uint8_t layerCount: Number of layers in the array

Notes

  • Combines several 1bpp layers with different colors
  • Layers are drawn in array order
  • Enables multi-color sprites without higher bit-depths
  • More layers = more draw calls (but still efficient)

Example

// Outline layer
static const uint16_t OUTLINE_DATA[] = {
    0b11111111,
    0b10000001,
    0b10000001,
    0b11111111
};

// Fill layer
static const uint16_t FILL_DATA[] = {
    0b00000000,
    0b01111110,
    0b01111110,
    0b00000000
};

static const SpriteLayer LAYERS[] = {
    {OUTLINE_DATA, Color::Black},  // Layer 0: Black outline
    {FILL_DATA, Color::Red}       // Layer 1: Red fill
};

static const MultiSprite PLAYER_MULTISPRITE = {
    8,      // width
    8,      // height
    LAYERS, // layers array
    2       // layer count
};

Sprite2bpp Structure (Experimental)

2-bit per pixel sprite (4 colors).

Requires: PIXELROOT32_ENABLE_2BPP_SPRITES build flag

Members

  • const uint8_t* data: Packed 2bpp data
  • const Color* palette: Local palette (4 colors)
  • uint8_t width: Sprite width
  • uint8_t height: Sprite height
  • uint8_t paletteSize: Number of colors (typically 4)

Notes

  • Experimental feature
  • Uses more memory than 1bpp
  • Each pixel can be one of 4 colors from local palette

Sprite4bpp Structure (Experimental)

4-bit per pixel sprite (16 colors).

Requires: PIXELROOT32_ENABLE_4BPP_SPRITES build flag

Members

  • const uint8_t* data: Packed 4bpp data
  • const Color* palette: Local palette (16 colors)
  • uint8_t width: Sprite width
  • uint8_t height: Sprite height
  • uint8_t paletteSize: Number of colors (typically 16)

Notes

  • Experimental feature
  • Uses more memory than 1bpp/2bpp
  • Each pixel can be one of 16 colors from local palette

Sprite Animation

SpriteAnimationFrame Structure

Frame that can reference either a Sprite or a MultiSprite.

Members: - const Sprite* sprite: Optional pointer to a simple 1bpp sprite frame - const MultiSprite* multiSprite: Optional pointer to a layered sprite frame

Notes: - Exactly one pointer should be non-null for a valid frame - Allows same animation system for both sprite types

SpriteAnimation Structure

Lightweight, step-based sprite animation controller.

Members: - const SpriteAnimationFrame* frames: Pointer to immutable frame table - uint8_t frameCount: Number of frames in the table - uint8_t current: Current frame index [0, frameCount)

Methods: - void reset(): Reset to first frame - void step(): Advance to next frame (wrapping) - const SpriteAnimationFrame& getCurrentFrame() const: Get current frame - const Sprite* getCurrentSprite() const: Get current simple sprite - const MultiSprite* getCurrentMultiSprite() const: Get current multi-sprite

Example:

static const SpriteAnimationFrame WALK_FRAMES[] = {
    {&walkFrame1, nullptr},
    {&walkFrame2, nullptr},
    {&walkFrame3, nullptr},
    {&walkFrame2, nullptr}  // Loop back
};

static SpriteAnimation walkAnimation = {
    WALK_FRAMES,
    4,    // frameCount
    0     // current
};

// In update
walkAnimation.step();
const Sprite* currentSprite = walkAnimation.getCurrentSprite();

Usage Examples

Creating 1bpp Sprites

// 8x8 player sprite
static const uint16_t PLAYER_DATA[] = {
    0b00111100,
    0b01111110,
    0b11111111,
    0b11011011,
    0b11111111,
    0b01111110,
    0b00111100,
    0b00011000
};

static const Sprite PLAYER_SPRITE = {
    PLAYER_DATA,
    8,  // width
    8   // height
};

// Use in rendering
renderer.drawSprite(PLAYER_SPRITE, 100, 100, Color::White);

Creating MultiSprite

// Outline layer
static const uint16_t OUTLINE[] = {
    0b11111111,
    0b10000001,
    0b10000001,
    0b11111111
};

// Fill layer
static const uint16_t FILL[] = {
    0b00000000,
    0b01111110,
    0b01111110,
    0b00000000
};

static const SpriteLayer LAYERS[] = {
    {OUTLINE, Color::Black},
    {FILL, Color::Red}
};

static const MultiSprite ENEMY_SPRITE = {
    8,      // width
    8,      // height
    LAYERS, // layers
    2       // layer count
};

// Use in rendering
renderer.drawMultiSprite(ENEMY_SPRITE, 100, 100);

Sprite Animation

class AnimatedActor : public pixelroot32::core::Actor {
private:
    SpriteAnimation animation;
    unsigned long animTimer = 0;
    unsigned long animInterval = 200;  // 200ms per frame

public:
    void update(unsigned long deltaTime) override {
        Actor::update(deltaTime);

        animTimer += deltaTime;
        if (animTimer >= animInterval) {
            animTimer -= animInterval;
            animation.step();
        }
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        const Sprite* frame = animation.getCurrentSprite();
        if (frame) {
            renderer.drawSprite(*frame, 
                               static_cast<int>(x), 
                               static_cast<int>(y), 
                               Color::White);
        }
    }
};

Sprite Flipping

Sprites can be flipped horizontally:

// Draw normal
renderer.drawSprite(sprite, 100, 100, Color::White, false);

// Draw flipped
renderer.drawSprite(sprite, 100, 100, Color::White, true);

Performance Considerations

  • 1bpp sprites: Most efficient (integer-only operations)
  • MultiSprite: Each layer is a separate draw call (still efficient)
  • 2bpp/4bpp: Experimental, uses more memory and CPU
  • Storage: Store sprite data in flash (const/constexpr) for best performance
  • Size limit: Sprites are limited to 16 pixels wide for 1bpp format

ESP32 Considerations

  • Memory: Store sprite data in flash, not RAM
  • Sprite size: Smaller sprites = faster drawing
  • Format choice: Use 1bpp when possible for best performance
  • MultiSprite: More layers = more draw calls (but acceptable)

See Also