Skip to content

Extensibility

PixelRoot32 is designed to be extensible. This guide covers how to create custom drivers, audio backends, and extend existing systems.

Creating Custom Display Drivers

To support a new display, implement the DrawSurface interface.

DrawSurface Interface

#include <graphics/DrawSurface.h>

class MyCustomDrawer : public pixelroot32::graphics::DrawSurface {
public:
    // Required methods
    void init() override;
    void setRotation(uint8_t rotation) override;
    void clearBuffer() override;
    void sendBuffer() override;

    // Drawing primitives
    void drawPixel(int x, int y, uint16_t color) override;
    void drawLine(int x1, int y1, int x2, int y2, uint16_t color) override;
    void drawRectangle(int x, int y, int width, int height, uint16_t color) override;
    void drawFilledRectangle(int x, int y, int width, int height, uint16_t color) override;
    void drawCircle(int x, int y, int radius, uint16_t color) override;
    void drawFilledCircle(int x, int y, int radius, uint16_t color) override;
    void drawBitmap(int x, int y, int width, int height, const uint8_t* bitmap, uint16_t color) override;

    // Text (deprecated, but must implement)
    void drawText(const char* text, int16_t x, int16_t y, uint16_t color, uint8_t size) override;
    void drawTextCentered(const char* text, int16_t y, uint16_t color, uint8_t size) override;

    // State management
    void setTextColor(uint16_t color) override;
    void setTextSize(uint8_t size) override;
    void setCursor(int16_t x, int16_t y) override;
    void setContrast(uint8_t level) override;
    void setDisplaySize(int w, int h) override;

    // Utilities
    uint16_t color565(uint8_t r, uint8_t g, uint8_t b) override;
    bool processEvents() override;
    void present() override;
};

Example: Simple Custom Drawer

#include <graphics/DrawSurface.h>

class SimpleDrawer : public pixelroot32::graphics::DrawSurface {
private:
    uint16_t* framebuffer;
    int width, height;

public:
    SimpleDrawer(int w, int h) : width(w), height(h) {
        framebuffer = new uint16_t[w * h];
    }

    ~SimpleDrawer() {
        delete[] framebuffer;
    }

    void init() override {
        // Initialize your display hardware
        // Clear framebuffer
        clearBuffer();
    }

    void clearBuffer() override {
        for (int i = 0; i < width * height; i++) {
            framebuffer[i] = 0x0000; // Black
        }
    }

    void sendBuffer() override {
        // Send framebuffer to display
        // Implementation depends on your hardware
    }

    void drawPixel(int x, int y, uint16_t color) override {
        if (x >= 0 && x < width && y >= 0 && y < height) {
            framebuffer[y * width + x] = color;
        }
    }

    void drawFilledRectangle(int x, int y, int w, int h, uint16_t color) override {
        for (int py = y; py < y + h; py++) {
            for (int px = x; px < x + w; px++) {
                drawPixel(px, py, color);
            }
        }
    }

    // Implement other required methods...
    // (See TFT_eSPI_Drawer or SDL2_Drawer for reference implementations)
};

Integrating Custom Driver

// Create custom drawer
SimpleDrawer* customDrawer = new SimpleDrawer(240, 240);

// Create renderer with custom drawer
pixelroot32::graphics::DisplayConfig config(
    pixelroot32::graphics::DisplayType::NONE, // Use NONE for custom
    0, 240, 240
);

// You'll need to modify Engine to accept custom DrawSurface
// Or create a custom Engine wrapper

Creating Custom Audio Backends

Implement the AudioBackend interface for custom audio hardware.

AudioBackend Interface

#include <audio/AudioBackend.h>

class MyCustomAudioBackend : public pixelroot32::audio::AudioBackend {
public:
    // Required methods
    void init() override;
    void start() override;
    void stop() override;
    uint32_t getSampleRate() const override;

    // Audio generation
    int16_t generateSample() override;

    // Channel management
    void setChannelWave(int channel, pixelroot32::audio::WaveType type, float frequency, float duty) override;
    void setChannelVolume(int channel, float volume) override;
    void stopChannel(int channel) override;
};

Example: Custom Audio Backend

#include <audio/AudioBackend.h>

class CustomAudioBackend : public pixelroot32::audio::AudioBackend {
private:
    uint32_t sampleRate;
    float phase[4] = {0, 0, 0, 0}; // 4 channels
    float frequency[4] = {0, 0, 0, 0};
    float volume[4] = {0, 0, 0, 0};
    pixelroot32::audio::WaveType waveType[4];

public:
    CustomAudioBackend(uint32_t rate) : sampleRate(rate) {
        for (int i = 0; i < 4; i++) {
            waveType[i] = pixelroot32::audio::WaveType::PULSE;
            volume[i] = 0.0f;
        }
    }

    void init() override {
        // Initialize your audio hardware
    }

    void start() override {
        // Start audio output
    }

    void stop() override {
        // Stop audio output
    }

    uint32_t getSampleRate() const override {
        return sampleRate;
    }

    int16_t generateSample() override {
        float sample = 0.0f;

        for (int ch = 0; ch < 4; ch++) {
            if (frequency[ch] > 0 && volume[ch] > 0) {
                float phaseIncrement = frequency[ch] / sampleRate;
                phase[ch] += phaseIncrement;
                if (phase[ch] >= 1.0f) phase[ch] -= 1.0f;

                float channelSample = 0.0f;
                switch (waveType[ch]) {
                    case pixelroot32::audio::WaveType::PULSE:
                        channelSample = (phase[ch] < 0.5f) ? 1.0f : -1.0f;
                        break;
                    case pixelroot32::audio::WaveType::TRIANGLE:
                        channelSample = (phase[ch] < 0.5f) ? 
                            (phase[ch] * 4.0f - 1.0f) : 
                            (3.0f - phase[ch] * 4.0f);
                        break;
                    // ... other wave types
                }

                sample += channelSample * volume[ch];
            }
        }

        // Clamp and convert to int16_t
        if (sample > 1.0f) sample = 1.0f;
        if (sample < -1.0f) sample = -1.0f;
        return static_cast<int16_t>(sample * 32767.0f);
    }

    void setChannelWave(int ch, pixelroot32::audio::WaveType type, 
                       float freq, float duty) override {
        if (ch >= 0 && ch < 4) {
            waveType[ch] = type;
            frequency[ch] = freq;
        }
    }

    void setChannelVolume(int ch, float vol) override {
        if (ch >= 0 && ch < 4) {
            volume[ch] = vol;
        }
    }

    void stopChannel(int ch) override {
        if (ch >= 0 && ch < 4) {
            frequency[ch] = 0.0f;
            volume[ch] = 0.0f;
        }
    }
};

Extending Existing Systems

Custom Entity Types

Create specialized entity types:

class PowerUpActor : public pixelroot32::core::Actor {
private:
    PowerUpType type;
    float lifetime = 5.0f; // 5 seconds

public:
    PowerUpActor(float x, float y, PowerUpType t)
        : Actor(x, y, 8, 8), type(t) {
        setRenderLayer(1);
        setCollisionLayer(Layers::POWERUP);
        setCollisionMask(Layers::PLAYER);
    }

    void update(unsigned long deltaTime) override {
        lifetime -= deltaTime * 0.001f;
        if (lifetime <= 0) {
            isEnabled = false;
            isVisible = false;
        }

        // Animate (bob up and down)
        y += sin(millis() * 0.005f) * 0.5f;
    }

    void onCollision(pixelroot32::core::Actor* other) override {
        if (other->isInLayer(Layers::PLAYER)) {
            applyPowerUp(other);
            isEnabled = false;
            isVisible = false;
        }
    }

private:
    void applyPowerUp(pixelroot32::core::Actor* player) {
        switch (type) {
            case PowerUpType::SPEED:
                // Increase player speed
                break;
            case PowerUpType::HEALTH:
                // Restore health
                break;
            // ...
        }
    }
};

Custom UI Layouts

Create new layout types:

#include <graphics/ui/UILayout.h>

class UICircularLayout : public pixelroot32::graphics::ui::UILayout {
private:
    float radius;
    float startAngle;

public:
    UICircularLayout(float x, float y, float w, float h, float r)
        : UILayout(x, y, w, h), radius(r), startAngle(0.0f) {
    }

    void updateLayout() override {
        int count = elements.size();
        float angleStep = 360.0f / count;

        for (size_t i = 0; i < elements.size(); i++) {
            float angle = startAngle + (i * angleStep);
            float rad = angle * M_PI / 180.0f;

            float elementX = x + (radius * cos(rad)) - (elements[i]->width / 2);
            float elementY = y + (radius * sin(rad)) - (elements[i]->height / 2);

            elements[i]->x = elementX;
            elements[i]->y = elementY;
        }
    }

    void handleInput(const pixelroot32::input::InputManager& input) override {
        // Implement circular navigation
        // ...
    }
};

Custom Collision Primitives

Extend collision system with new shapes:

// Add to your game code (not engine modification)
struct Triangle {
    float x1, y1, x2, y2, x3, y3;
};

bool intersects(const Triangle& tri, const pixelroot32::core::Rect& rect) {
    // Implement triangle-rectangle intersection
    // ...
    return false;
}

bool intersects(const Triangle& tri1, const Triangle& tri2) {
    // Implement triangle-triangle intersection
    // ...
    return false;
}

Best Practices

Maintain Compatibility

  • Don't break existing APIs: Extend, don't modify
  • Use inheritance: Inherit from base classes
  • Follow patterns: Match existing code patterns
  • Document extensions: Comment your custom code

Testing

  • Test on both platforms: ESP32 and Native
  • Test edge cases: Boundary conditions, null pointers
  • Performance testing: Ensure extensions don't hurt performance
  • Memory testing: Check for leaks with custom code

Documentation

  • Comment your code: Explain why, not just what
  • Provide examples: Show how to use your extensions
  • Document limitations: State what doesn't work
  • Version compatibility: Note which engine version

Common Extension Patterns

Factory Pattern

class EntityFactory {
public:
    static pixelroot32::core::Entity* createEnemy(EnemyType type, float x, float y) {
        switch (type) {
            case EnemyType::BASIC:
                return new BasicEnemy(x, y);
            case EnemyType::FAST:
                return new FastEnemy(x, y);
            case EnemyType::TANK:
                return new TankEnemy(x, y);
            default:
                return nullptr;
        }
    }

    static pixelroot32::core::Entity* createPowerUp(PowerUpType type, float x, float y) {
        return new PowerUpActor(x, y, type);
    }
};

Strategy Pattern

class MovementStrategy {
public:
    virtual void update(pixelroot32::core::Actor* actor, unsigned long deltaTime) = 0;
};

class LinearMovement : public MovementStrategy {
public:
    void update(pixelroot32::core::Actor* actor, unsigned long deltaTime) override {
        actor->x += speed * (deltaTime * 0.001f);
    }
};

class CircularMovement : public MovementStrategy {
public:
    void update(pixelroot32::core::Actor* actor, unsigned long deltaTime) override {
        float angle = millis() * 0.001f;
        actor->x = centerX + radius * cos(angle);
        actor->y = centerY + radius * sin(angle);
    }
};

class SmartEnemy : public pixelroot32::core::Actor {
private:
    MovementStrategy* movement;

public:
    void setMovement(MovementStrategy* strat) {
        movement = strat;
    }

    void update(unsigned long deltaTime) override {
        if (movement) {
            movement->update(this, deltaTime);
        }
    }
};

Troubleshooting

Driver Not Working

  • Verify all interface methods are implemented
  • Check initialization order
  • Test with simple drawing first
  • Verify hardware connections

Audio Backend Issues

  • Check sample rate matches hardware
  • Verify audio generation logic
  • Test with simple tones first
  • Check channel management

Extension Conflicts

  • Ensure namespace isolation
  • Avoid modifying engine code directly
  • Use composition over modification
  • Test with engine updates

Next Steps

Now that you understand extensibility, you've completed the optimization section. Continue with: - API Reference - Complete API documentation - Examples - Code examples - Resources - Tools and troubleshooting


See also: - API Reference - DrawSurface - API Reference - AudioBackend - Manual - Platforms and Drivers