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