Skip to content

Entities & Actors ​

Entities and Actors form the object system in PixelRoot32. Understanding their relationship and capabilities is essential for building game objects.

Entity Hierarchy ​

Entity ​

Entity is the base class for all objects in a scene. It provides:

  • Transform: Position, width, height
  • Lifecycle: update() and draw() methods
  • State: Visibility, enabled status, render layer
  • Classification: Entity type for safe casting
cpp
#include <Entity.h>

using namespace pixelroot32;

class Background : public core::Entity {
public:
    Background() : Entity(0, 0, 240, 240, EntityType::GENERIC) {
        setRenderLayer(0);  // Draw first (background)
    }
    
    void update(unsigned long deltaTime) override {
        // Background typically doesn't update
        (void)deltaTime;
    }
    
    void draw(graphics::Renderer& r) override {
        // Draw background gradient or image
        r.drawFilledRectangle(0, 0, width, height, graphics::Color::BLUE);
    }
};

Entity Properties ​

PropertyTypeDescription
positionVector2World-space position (top-left)
width / heightintDimensions in pixels
typeEntityTypeClassification enum
isVisibleboolControls draw() calls
isEnabledboolControls update() calls
renderLayerunsigned charDrawing order (0 = back)

Render Layers ​

Entities are drawn from lowest to highest layer:

cpp
// Layer 0: Background
background->setRenderLayer(0);

// Layer 1: Game objects
player->setRenderLayer(1);
enemy->setRenderLayer(1);
item->setRenderLayer(1);

// Layer 2: Effects (particles, etc.)
explosion->setRenderLayer(2);

// Layer 3: UI/HUD
scoreDisplay->setRenderLayer(3);
healthBar->setRenderLayer(3);

Actor ​

Actor extends Entity with collision capabilities. All physics-enabled objects are Actors.

cpp
#include <Actor.h>

using namespace pixelroot32;

class Coin : public core::Actor {
public:
    Coin(int x, int y) : Actor(x, y, 8, 8) {
        // Set up collision filtering
        setCollisionLayer(physics::DefaultLayers::kItem);
        setCollisionMask(physics::DefaultLayers::kPlayer);
    }
    
    void update(unsigned long deltaTime) override {
        // Rotate animation
        rotation += deltaTime * 0.1f;
    }
    
    void draw(graphics::Renderer& r) override {
        // Draw coin sprite with rotation
        r.drawSprite(sprite, position.x, position.y, graphics::Color::YELLOW);
    }
    
    void onCollision(core::Actor* other) override {
        // Only collides with player due to mask
        if (other->isInLayer(physics::DefaultLayers::kPlayer)) {
            collect();
        }
    }
    
    void collect() {
        isVisible = false;  // Hide (will be cleaned up)
        // Award points...
    }
    
private:
    float rotation = 0;
    const graphics::Sprite* sprite;
};

Collision Filtering ​

Actors use a layer/mask system for selective collision:

cpp
// Default layers provided by engine
namespace physics::DefaultLayers {
    constexpr CollisionLayer kNone = 0;
    constexpr CollisionLayer kPlayer = 1 << 0;
    constexpr CollisionLayer kEnemy = 1 << 1;
    constexpr CollisionLayer kEnvironment = 1 << 2;
    constexpr CollisionLayer kItem = 1 << 3;
    constexpr CollisionLayer kProjectile = 1 << 4;
}

// Player collides with environment and items
player->setCollisionLayer(DefaultLayers::kPlayer);
player->setCollisionMask(DefaultLayers::kEnvironment | DefaultLayers::kItem);

// Enemy collides with environment and player projectiles
enemy->setCollisionLayer(DefaultLayers::kEnemy);
enemy->setCollisionMask(DefaultLayers::kEnvironment | DefaultLayers::kProjectile);

// Item only collides with player
item->setCollisionLayer(DefaultLayers::kItem);
item->setCollisionMask(DefaultLayers::kPlayer);

Actor Types ​

PixelRoot32 provides four specialized Actor types for different use cases:

StaticActor ​

For immovable world geometry—walls, floors, platforms.

cpp
#include <StaticActor.h>

using namespace pixelroot32::physics;

// Create a ground platform
auto* ground = new StaticActor(0, 200, 240, 40);
ground->setCollisionLayer(DefaultLayers::kEnvironment);
scene.addEntity(ground);

Characteristics:

  • Zero mass (infinite inertia)
  • Never moves
  • Other actors collide and respond

KinematicActor ​

For player-controlled or AI-driven objects that need collision response.

cpp
#include <KinematicActor.h>

using namespace pixelroot32;

class Player : public physics::KinematicActor {
public:
    Player() : KinematicActor(120, 120, 16, 16) {
        setCollisionLayer(physics::DefaultLayers::kPlayer);
        setCollisionMask(physics::DefaultLayers::kEnvironment);
    }
    
    void update(unsigned long deltaTime) override {
        using namespace math;
        
        // Build velocity from input
        Vector2 velocity;
        
        if (input.isButtonPressed(ButtonName::LEFT)) {
            velocity.x = -speed;
        } else if (input.isButtonPressed(ButtonName::RIGHT)) {
            velocity.x = speed;
        }
        
        if (isOnFloor() && input.isButtonPressed(ButtonName::A)) {
            velocity.y = -jumpForce;  // Jump
        }
        
        // Apply gravity
        velocity.y += gravity * toScalar(deltaTime) / toScalar(1000);
        
        // Move with collision response
        velocity *= toScalar(deltaTime) / toScalar(1000);
        auto collision = moveAndSlide(velocity, deltaTime);
        
        // Handle collision results
        if (collision.collides && collision.normal.y < 0) {
            // Hit ceiling
        }
    }
    
    void onCollision(core::Actor* other) override {
        // Handle special collisions
        if (other->isInLayer(physics::DefaultLayers::kItem)) {
            collectItem(static_cast<Item*>(other));
        }
    }
    
private:
    Scalar speed = toScalar(150);  // pixels/second
    Scalar jumpForce = toScalar(300);
    Scalar gravity = toScalar(600);
};

Key methods:

MethodDescription
moveAndSlide(velocity, dt)Move with sliding along surfaces
moveAndCollide(velocity, dt)Move and stop on collision
isOnFloor()True if standing on something
isOnWall()True if touching a wall
isOnCeiling()True if touching ceiling

RigidActor ​

For physics-driven objects affected by forces.

cpp
#include <RigidActor.h>

using namespace pixelroot32;

class Crate : public physics::RigidActor {
public:
    Crate(int x, int y) : RigidActor(x, y, 16, 16) {
        setCollisionLayer(physics::DefaultLayers::kEnvironment);
        mass = 10.0f;
        friction = 0.5f;
        bounce = 0.2f;
    }
    
    void onCollision(core::Actor* other) override {
        // Crates can be pushed by player
        if (other->isInLayer(physics::DefaultLayers::kPlayer)) {
            // Momentum transfer handled by physics system
        }
    }
};

// Spawn and apply force
auto* crate = new Crate(100, 50);
scene.addEntity(crate);

crate->applyImpulse(math::Vector2(50, -100));  // Throw crate

Properties:

PropertyDescription
massKilograms (affects momentum)
velocityCurrent velocity vector
friction0-1, affects sliding
bounce0-1, restitution coefficient
gravityScaleMultiplier for gravity

SensorActor ​

For trigger zones—detection without physical response.

cpp
#include <SensorActor.h>

using namespace pixelroot32;

class DeathZone : public physics::SensorActor {
public:
    DeathZone(int x, int y, int w, int h) 
        : SensorActor(x, y, w, h) {
        setCollisionLayer(physics::DefaultLayers::kSensor);
        setCollisionMask(physics::DefaultLayers::kPlayer);
    }
    
    void onCollision(core::Actor* other) override {
        // Kill player (no physics response, just notification)
        if (other->isInLayer(physics::DefaultLayers::kPlayer)) {
            gameOver();
        }
    }
};

// Checkpoint trigger
class Checkpoint : public physics::SensorActor {
    bool activated = false;
    
public:
    void onCollision(core::Actor* other) override {
        if (!activated && other->isInLayer(physics::DefaultLayers::kPlayer)) {
            activated = true;
            saveGame();
            showActivatedEffect();
        }
    }
};

Sensors:

  • Detect overlaps without blocking movement
  • Generate onCollision callbacks
  • No physics response (velocity unaffected)

Creating Custom Entities ​

Basic Entity Pattern ​

cpp
class MyEntity : public core::Entity {
public:
    MyEntity(int x, int y) 
        : Entity(x, y, 32, 32, EntityType::GENERIC) {
        // Initialize
    }
    
    void update(unsigned long deltaTime) override {
        // Update logic
    }
    
    void draw(graphics::Renderer& r) override {
        // Render
    }
};

Custom Actor Pattern ​

cpp
class Projectile : public physics::KinematicActor {
    unsigned long lifetime = 2000;  // 2 seconds
    unsigned long age = 0;
    
public:
    Projectile(int x, int y, Vector2 direction) 
        : KinematicActor(x, y, 4, 4) {
        setCollisionLayer(DefaultLayers::kProjectile);
        setCollisionMask(DefaultLayers::kEnemy | DefaultLayers::kEnvironment);
        
        velocity = direction * speed;
    }
    
    void update(unsigned long deltaTime) override {
        age += deltaTime;
        
        if (age > lifetime) {
            destroy();  // Remove from scene
            return;
        }
        
        // Move without gravity
        auto collision = moveAndCollide(velocity * dt, deltaTime);
        
        if (collision.collides) {
            onHit(collision);
        }
    }
    
    void onCollision(core::Actor* other) override {
        if (other->isInLayer(DefaultLayers::kEnemy)) {
            damageEnemy(static_cast<Enemy*>(other));
            destroy();
        }
    }
    
    void draw(graphics::Renderer& r) override {
        r.drawFilledRectangle(
            position.x, position.y, 
            width, height, 
            graphics::Color::YELLOW
        );
    }
    
private:
    Scalar speed = toScalar(400);
    Vector2 velocity;
    
    void destroy() {
        isVisible = false;
        isEnabled = false;
        // Scene cleanup will remove
    }
};

Entity Management Best Practices ​

Object Ownership ​

cpp
class GameScene : public Scene {
    // Option 1: Smart pointers (recommended)
    std::unique_ptr<Player> player;
    std::vector<std::unique_ptr<Enemy>> enemies;
    
    // Option 2: Raw pointers (scene arena)
    Player* player;
    
public:
    void init() override {
        // Smart pointer approach
        player = std::make_unique<Player>(100, 100);
        addEntity(player.get());
        
        // Arena approach
        player = arenaNew<Player>(arena, 100, 100);
        addEntity(player);
    }
};

Entity Lifecycle ​

Performance Tips ​

  1. Reuse entities instead of creating/destroying:

    cpp
    // Good: Pool pattern
    Projectile* spawnProjectile() {
        for (auto* p : projectilePool) {
            if (!p->isEnabled) {
                p->reset(x, y);
                p->isEnabled = true;
                return p;
            }
        }
        return nullptr;  // Pool exhausted
    }
  2. Pre-filter updates:

    cpp
    void update(unsigned long dt) override {
        if (!shouldUpdate) return;  // Early out
        // ... expensive logic
    }
  3. Batch render calls:

    cpp
    void draw(Renderer& r) override {
        // Bad: Many draw calls
        for (int i = 0; i < 100; ++i) {
            r.drawPixel(x + i, y, color);
        }
        
        // Good: Single draw call
        r.drawFilledRectangle(x, y, 100, 1, color);
    }

Next Steps ​

  • Scenes — How entities live in scenes
  • Physics — Deep dive into collision and movement
  • Rendering — Drawing techniques and optimization

Released under the MIT License.