Skip to content

Code Examples

A library of reusable code snippets for common PixelRoot32 tasks. All examples are complete and functional.

Initialization

Basic Engine Setup (ESP32)

#include <Arduino.h>
#include <core/Engine.h>
#include <drivers/esp32/TFT_eSPI_Drawer.h>
#include <drivers/esp32/ESP32_DAC_AudioBackend.h>

namespace pr32 = pixelroot32;

// Audio
const int DAC_PIN = 25;
pr32::drivers::esp32::ESP32_DAC_AudioBackend audioBackend(DAC_PIN, 11025);

// Display
pr32::graphics::DisplayConfig displayConfig(
    pr32::graphics::DisplayType::ST7789,
    0, 240, 240
);

// Input
pr32::input::InputConfig inputConfig(6, 32, 27, 33, 14, 13, 12);

// Audio config
pr32::audio::AudioConfig audioConfig(&audioBackend, 11025);

// Engine
pr32::core::Engine engine(displayConfig, inputConfig, audioConfig);

void setup() {
    Serial.begin(115200);
    engine.init();
    // ... scene setup
}

void loop() {
    engine.run();
}

Basic Engine Setup (Native)

#define SDL_MAIN_HANDLED
#include <SDL2/SDL.h>
#include <core/Engine.h>
#include <drivers/native/SDL2_Drawer.h>
#include <drivers/native/SDL2_AudioBackend.h>

namespace pr32 = pixelroot32;

pr32::drivers::native::SDL2_AudioBackend audioBackend(22050, 1024);
pr32::graphics::DisplayConfig displayConfig(
    pr32::graphics::DisplayType::NONE, 0, 240, 240
);
pr32::input::InputConfig inputConfig(
    6, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, 
    SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT,
    SDL_SCANCODE_SPACE, SDL_SCANCODE_RETURN
);
pr32::audio::AudioConfig audioConfig(&audioBackend, 22050);

pr32::core::Engine engine(displayConfig, inputConfig, audioConfig);

int main(int argc, char* argv[]) {
    engine.init();
    // ... scene setup
    engine.run();
    return 0;
}

Entity Movement

Simple Movement

class MovingEntity : public pixelroot32::core::Entity {
private:
    float speedX = 50.0f;
    float speedY = 30.0f;

public:
    MovingEntity(float x, float y)
        : Entity(x, y, 16, 16, pixelroot32::core::EntityType::GENERIC) {
        setRenderLayer(1);
    }

    void update(unsigned long deltaTime) override {
        float dt = deltaTime * 0.001f;
        x += speedX * dt;
        y += speedY * dt;

        // Bounce off edges
        if (x < 0 || x > 224) speedX = -speedX;
        if (y < 0 || y > 224) speedY = -speedY;
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        renderer.drawFilledRectangle(
            static_cast<int>(x),
            static_cast<int>(y),
            width, height,
            pixelroot32::graphics::Color::Cyan
        );
    }
};

Input-Based Movement

class PlayerEntity : public pixelroot32::core::Actor {
private:
    float speed = 100.0f;

public:
    PlayerEntity(float x, float y)
        : Actor(x, y, 16, 16) {
        setRenderLayer(1);
    }

    void update(unsigned long deltaTime) override {
        auto& input = engine.getInputManager();
        float dt = deltaTime * 0.001f;

        if (input.isButtonDown(Buttons::LEFT)) {
            x -= speed * dt;
        }
        if (input.isButtonDown(Buttons::RIGHT)) {
            x += speed * dt;
        }
        if (input.isButtonDown(Buttons::UP)) {
            y -= speed * dt;
        }
        if (input.isButtonDown(Buttons::DOWN)) {
            y += speed * dt;
        }

        // Keep on screen
        if (x < 0) x = 0;
        if (x > 224) x = 224;
        if (y < 0) y = 0;
        if (y > 224) y = 224;
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        renderer.drawFilledRectangle(
            static_cast<int>(x),
            static_cast<int>(y),
            width, height,
            pixelroot32::graphics::Color::White
        );
    }

    pixelroot32::core::Rect getHitBox() override {
        return {x, y, width, height};
    }

    void onCollision(pixelroot32::core::Actor* other) override {
        // Handle collision
    }
};

Collisions

Basic Collision Detection

class CollidableEntity : public pixelroot32::core::Actor {
public:
    CollidableEntity(float x, float y)
        : Actor(x, y, 16, 16) {
        setRenderLayer(1);
        setCollisionLayer(Layers::PLAYER);
        setCollisionMask(Layers::ENEMY | Layers::WALL);
    }

    void onCollision(pixelroot32::core::Actor* other) override {
        if (other->isInLayer(Layers::ENEMY)) {
            // Hit enemy
            takeDamage();
        } else if (other->isInLayer(Layers::WALL)) {
            // Hit wall
            stopMovement();
        }
    }

    pixelroot32::core::Rect getHitBox() override {
        return {x, y, width, height};
    }
};

Collision Layers Setup

// Define in GameLayers.h
namespace Layers {
    constexpr uint16_t PLAYER = 0x0001;
    constexpr uint16_t ENEMY = 0x0002;
    constexpr uint16_t PROJECTILE = 0x0004;
    constexpr uint16_t WALL = 0x0008;
    constexpr uint16_t PICKUP = 0x0010;
}

// Usage
player->setCollisionLayer(Layers::PLAYER);
player->setCollisionMask(Layers::ENEMY | Layers::WALL);

enemy->setCollisionLayer(Layers::ENEMY);
enemy->setCollisionMask(Layers::PLAYER | Layers::PROJECTILE);

Sound Effects

Common Sound Effects

namespace SoundEffects {
    inline pixelroot32::audio::AudioEvent jump() {
        pixelroot32::audio::AudioEvent evt{};
        evt.type = pixelroot32::audio::WaveType::PULSE;
        evt.frequency = 600.0f;
        evt.duration = 0.1f;
        evt.volume = 0.7f;
        evt.duty = 0.25f;
        return evt;
    }

    inline pixelroot32::audio::AudioEvent coin() {
        pixelroot32::audio::AudioEvent evt{};
        evt.type = pixelroot32::audio::WaveType::PULSE;
        evt.frequency = 1500.0f;
        evt.duration = 0.12f;
        evt.volume = 0.8f;
        evt.duty = 0.5f;
        return evt;
    }

    inline pixelroot32::audio::AudioEvent explosion() {
        pixelroot32::audio::AudioEvent evt{};
        evt.type = pixelroot32::audio::WaveType::NOISE;
        evt.frequency = 200.0f;
        evt.duration = 0.3f;
        evt.volume = 0.9f;
        return evt;
    }
}

// Usage
engine.getAudioEngine().playEvent(SoundEffects::jump());

Playing Sound on Event

class PlayerActor : public pixelroot32::core::Actor {
public:
    void update(unsigned long deltaTime) override {
        auto& input = engine.getInputManager();

        if (input.isButtonPressed(Buttons::A)) {
            // Play jump sound
            pixelroot32::audio::AudioEvent jumpSound{};
            jumpSound.type = pixelroot32::audio::WaveType::PULSE;
            jumpSound.frequency = 800.0f;
            jumpSound.duration = 0.1f;
            jumpSound.volume = 0.7f;
            jumpSound.duty = 0.25f;

            engine.getAudioEngine().playEvent(jumpSound);

            // Jump logic
            jump();
        }
    }
};

UI Components

Simple Menu

class MenuScene : public pixelroot32::core::Scene {
private:
    pixelroot32::graphics::ui::UIVerticalLayout* menu;

public:
    void init() override {
        menu = new pixelroot32::graphics::ui::UIVerticalLayout(40, 60, 160, 160);
        menu->setPadding(10);
        menu->setSpacing(8);
        menu->setNavigationButtons(0, 1);
        menu->setButtonStyle(
            pixelroot32::graphics::Color::White,
            pixelroot32::graphics::Color::Cyan,
            pixelroot32::graphics::Color::White,
            pixelroot32::graphics::Color::Black
        );

        menu->addElement(new pixelroot32::graphics::ui::UIButton(
            "Start", 0, 0, 0, 140, 25, []() { startGame(); }
        ));

        menu->addElement(new pixelroot32::graphics::ui::UIButton(
            "Options", 1, 0, 0, 140, 25, []() { showOptions(); }
        ));

        addEntity(menu);
    }

    void update(unsigned long deltaTime) override {
        menu->handleInput(engine.getInputManager());
        Scene::update(deltaTime);
    }
};

HUD with Labels

class GameHUD : public pixelroot32::core::Entity {
private:
    pixelroot32::graphics::ui::UILabel* scoreLabel;
    pixelroot32::graphics::ui::UILabel* livesLabel;

public:
    GameHUD()
        : Entity(0, 0, 240, 240, pixelroot32::core::EntityType::UI_ELEMENT) {
        setRenderLayer(2);

        scoreLabel = new pixelroot32::graphics::ui::UILabel(
            "Score: 0", 10, 10,
            pixelroot32::graphics::Color::White, 1
        );

        livesLabel = new pixelroot32::graphics::ui::UILabel(
            "Lives: 3", 10, 20,
            pixelroot32::graphics::Color::White, 1
        );
    }

    void updateHUD(int score, int lives) {
        char buffer[32];
        snprintf(buffer, sizeof(buffer), "Score: %d", score);
        scoreLabel->setText(buffer);

        snprintf(buffer, sizeof(buffer), "Lives: %d", lives);
        livesLabel->setText(buffer);
    }
};

Physics

Bouncing Ball

class BouncingBall : public pixelroot32::core::PhysicsActor {
public:
    BouncingBall(float x, float y, float radius)
        : PhysicsActor(x, y, radius * 2, radius * 2) {
        setRenderLayer(1);
        setRestitution(0.9f);
        setFriction(0.05f);
        setWorldSize(240, 240);
        setVelocity(50.0f, -30.0f);
    }

    void update(unsigned long deltaTime) override {
        float gravity = 200.0f;
        float dt = deltaTime * 0.001f;
        setVelocity(vx, vy + gravity * dt);
        PhysicsActor::update(deltaTime);
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        int radius = width / 2;
        renderer.drawFilledCircle(
            static_cast<int>(x + radius),
            static_cast<int>(y + radius),
            radius,
            pixelroot32::graphics::Color::Cyan
        );
    }

    pixelroot32::core::Rect getHitBox() override {
        return {x, y, width, height};
    }
};

Platformer Player

class PlatformerPlayer : public pixelroot32::core::PhysicsActor {
private:
    bool canJump = true;
    float jumpForce = 250.0f;
    float moveSpeed = 100.0f;

public:
    PlatformerPlayer(float x, float y)
        : PhysicsActor(x, y, 16, 16) {
        setRenderLayer(1);
        setFriction(0.3f);
        setWorldSize(240, 240);
    }

    void update(unsigned long deltaTime) override {
        auto& input = engine.getInputManager();
        float dt = deltaTime * 0.001f;

        // Horizontal movement
        float moveDir = 0.0f;
        if (input.isButtonDown(Buttons::LEFT)) moveDir -= 1.0f;
        if (input.isButtonDown(Buttons::RIGHT)) moveDir += 1.0f;
        setVelocity(moveDir * moveSpeed, vy);

        // Gravity
        float gravity = 300.0f;
        setVelocity(vx, vy + gravity * dt);

        // Jump
        if (input.isButtonPressed(Buttons::A) && canJump) {
            setVelocity(vx, -jumpForce);
            canJump = false;
        }

        PhysicsActor::update(deltaTime);

        // Check if on ground
        auto collisionInfo = getWorldCollisionInfo();
        if (collisionInfo.bottom) {
            canJump = true;
        }
    }

    pixelroot32::core::Rect getHitBox() override {
        return {x, y, width, height};
    }
};

Sprites and Animation

Simple Sprite

// Define sprite data
static const uint16_t PLAYER_SPRITE_DATA[] = {
    0b00111100,
    0b01111110,
    0b11111111,
    0b11111111,
    0b11111111,
    0b01111110,
    0b00111100,
    0b00000000
};

static const pixelroot32::graphics::Sprite PLAYER_SPRITE = {
    PLAYER_SPRITE_DATA, 8, 8
};

// Draw sprite
renderer.drawSprite(PLAYER_SPRITE, 100, 100, 
    pixelroot32::graphics::Color::White);

Sprite Animation

class AnimatedActor : public pixelroot32::core::Actor {
private:
    pixelroot32::graphics::SpriteAnimation animation;
    unsigned long timer = 0;
    const unsigned long FRAME_DURATION_MS = 100;

public:
    AnimatedActor(float x, float y)
        : Actor(x, y, 8, 8) {
        setRenderLayer(1);
        animation.frames = WALK_ANIMATION_FRAMES;
        animation.frameCount = 3;
        animation.current = 0;
    }

    void update(unsigned long deltaTime) override {
        timer += deltaTime;
        if (timer >= FRAME_DURATION_MS) {
            timer -= FRAME_DURATION_MS;
            animation.step();
        }
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        const auto* frame = animation.frames[animation.current].sprite;
        renderer.drawSprite(*frame, static_cast<int>(x), static_cast<int>(y),
            pixelroot32::graphics::Color::White);
    }
};

Camera and Scrolling

Basic Camera Follow

class ScrollingScene : public pixelroot32::core::Scene {
private:
    pixelroot32::graphics::Camera2D camera;
    PlayerActor* player;

public:
    void init() override {
        int screenWidth = engine.getRenderer().getWidth();
        int screenHeight = engine.getRenderer().getHeight();

        camera = pixelroot32::graphics::Camera2D(screenWidth, screenHeight);
        camera.setBounds(0, 2000 - screenWidth);

        player = new PlayerActor(100, 100);
        addEntity(player);
    }

    void update(unsigned long deltaTime) override {
        Scene::update(deltaTime);
        camera.followTarget(player->x, player->y);
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        camera.apply(renderer);
        Scene::draw(renderer);
    }
};

Tilemaps

Simple Tilemap

// Define tiles
static const uint16_t TILE_EMPTY_BITS[] = { /* ... */ };
static const uint16_t TILE_GROUND_BITS[] = { /* ... */ };

static const pixelroot32::graphics::Sprite TILES[] = {
    { TILE_EMPTY_BITS, 8, 8 },
    { TILE_GROUND_BITS, 8, 8 }
};

// Create tilemap
static uint8_t TILEMAP_INDICES[30 * 20];
static pixelroot32::graphics::TileMap levelTileMap = {
    TILEMAP_INDICES, 30, 20, TILES, 8, 8, 2
};

// Initialize
void initTilemap() {
    for (int i = 0; i < 30 * 20; i++) {
        TILEMAP_INDICES[i] = 0;
    }
    // Set ground row
    for (int x = 0; x < 30; x++) {
        TILEMAP_INDICES[19 * 30 + x] = 1; // Ground tile
    }
}

// Draw
renderer.drawTileMap(levelTileMap, 0, 0, 
    pixelroot32::graphics::Color::White);

Particles

Explosion Effect

#include <graphics/particles/ParticleEmitter.h>
#include <graphics/particles/ParticlePresets.h>

class ExplosionEffect : public pixelroot32::core::Entity {
private:
    pixelroot32::graphics::particles::ParticleEmitter* emitter;

public:
    ExplosionEffect()
        : Entity(0, 0, 1, 1, pixelroot32::core::EntityType::GENERIC) {
        setRenderLayer(1);
        emitter = new pixelroot32::graphics::particles::ParticleEmitter(
            0, 0,
            pixelroot32::graphics::particles::ParticlePresets::Explosion()
        );
    }

    void trigger(float x, float y) {
        this->x = x;
        this->y = y;
        emitter->burst(x, y, 25);
    }

    void update(unsigned long deltaTime) override {
        emitter->update(deltaTime);
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        emitter->draw(renderer);
    }
};

Object Pooling

Entity Pool

template<typename T, int POOL_SIZE>
class EntityPool {
private:
    T pool[POOL_SIZE];
    bool inUse[POOL_SIZE];
    int activeCount = 0;

public:
    EntityPool() {
        for (int i = 0; i < POOL_SIZE; i++) {
            inUse[i] = false;
        }
    }

    T* acquire() {
        if (activeCount >= POOL_SIZE) return nullptr;
        for (int i = 0; i < POOL_SIZE; i++) {
            if (!inUse[i]) {
                inUse[i] = true;
                activeCount++;
                return &pool[i];
            }
        }
        return nullptr;
    }

    void release(T* obj) {
        for (int i = 0; i < POOL_SIZE; i++) {
            if (&pool[i] == obj) {
                inUse[i] = false;
                activeCount--;
                obj->isEnabled = false;
                obj->isVisible = false;
                break;
            }
        }
    }
};

Common Patterns

State Machine

enum class GameState {
    MENU,
    PLAYING,
    PAUSED,
    GAME_OVER
};

class GameScene : public pixelroot32::core::Scene {
private:
    GameState currentState = GameState::MENU;

public:
    void update(unsigned long deltaTime) override {
        switch (currentState) {
            case GameState::MENU:
                updateMenu();
                break;
            case GameState::PLAYING:
                updateGame();
                break;
            case GameState::PAUSED:
                updatePause();
                break;
            case GameState::GAME_OVER:
                updateGameOver();
                break;
        }
        Scene::update(deltaTime);
    }
};

Timer Pattern

class Timer {
private:
    unsigned long duration;
    unsigned long elapsed = 0;
    bool active = false;

public:
    Timer(unsigned long ms) : duration(ms) {}

    void start() {
        active = true;
        elapsed = 0;
    }

    void update(unsigned long deltaTime) {
        if (active) {
            elapsed += deltaTime;
            if (elapsed >= duration) {
                active = false;
            }
        }
    }

    bool isFinished() const { return !active && elapsed >= duration; }
    bool isActive() const { return active; }
    float getProgress() const { return static_cast<float>(elapsed) / duration; }
};

See Also