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¶
- API Reference Overview - Complete API documentation
- Game Examples Guide - Learn from complete games
- Manual - Game Development - Detailed guides