Skip to content

Entity System

Heaps uses a display list hierarchy based on h2d.Object. Understanding this system is key to organizing and rendering your game objects efficiently.

Object Hierarchy

Every visual element extends h2d.Object:

// Base display object
var obj = new h2d.Object(parent);

// Common display objects
var bitmap = new h2d.Bitmap(tile, parent);
var text = new h2d.Text(font, parent);
var graphics = new h2d.Graphics(parent);
var interactive = new h2d.Interactive(width, height, parent);

Parent-Child Relationships

// Create hierarchy
var world = new h2d.Object(s2d);
var player = new h2d.Object(world);
var weapon = new h2d.Object(player);

// Transform inheritance
player.x = 100;
player.y = 100;
weapon.x = 20; // Relative to player

// weapon's world position = (120, 100)

Creating Entities

Basic Entity Class

class Entity extends h2d.Object {
    public var velocity : h2d.col.Point;
    public var bounds : h2d.col.Bounds;

    public function new(?parent:h2d.Object) {
        super(parent);
        velocity = new h2d.col.Point();
        bounds = new h2d.col.Bounds();
    }

    public function update(dt:Float) {
        // Physics
        x += velocity.x * dt;
        y += velocity.y * dt;

        // Update bounds
        bounds.x = x - bounds.width * 0.5;
        bounds.y = y - bounds.height * 0.5;
    }
}

Composite Entity

class Player extends Entity {
    var sprite : h2d.Anim;
    var shadow : h2d.Bitmap;
    var healthBar : h2d.Graphics;

    public function new(?parent) {
        super(parent);

        // Shadow (rendered first)
        var shadowTile = h2d.Tile.fromColor(0x000000, 32, 16, 0.3);
        shadow = new h2d.Bitmap(shadowTile, this);
        shadow.center();
        shadow.y = 20;

        // Animated sprite
        var tiles = hxd.Res.sprites.player_walk.toTile().split(8);
        sprite = new h2d.Anim(tiles, 10, this);
        sprite.center();

        // Health bar (always on top)
        healthBar = new h2d.Graphics(this);
        updateHealthBar(100, 100);

        // Set bounds
        bounds.set(-16, -24, 32, 48);
    }

    function updateHealthBar(current:Int, max:Int) {
        healthBar.clear();

        // Background
        healthBar.beginFill(0x333333);
        healthBar.drawRect(-16, -30, 32, 4);

        // Health
        var pct = current / max;
        healthBar.beginFill(0x00ff00);
        healthBar.drawRect(-16, -30, 32 * pct, 4);

        healthBar.endFill();
    }
}

Display Properties

Transform Properties

var entity = new Entity();

// Position
entity.x = 100;
entity.y = 200;

// Scale
entity.scaleX = 2;
entity.scaleY = 2;
entity.scale(1.5); // Both axes

// Rotation (radians)
entity.rotation = Math.PI / 4;

// Visibility
entity.visible = false;
entity.alpha = 0.5;

Blend Modes

// Additive blending for lights/effects
entity.blendMode = Add;

// Multiply for shadows
entity.blendMode = Multiply;

// Default
entity.blendMode = Alpha;

// Other modes: None, Erase, Screen, Overlay

Filters

// Color matrix filter
var filter = new h2d.filter.ColorMatrix();
entity.filter = filter;

// Grayscale
filter.matrix = h2d.filter.ColorMatrix.grayed();

// Blur
entity.filter = new h2d.filter.Blur(2, 2);

// Glow
entity.filter = new h2d.filter.Glow(0xFF0000, 1, 10);

// Multiple filters
entity.filter = new h2d.filter.Group([
    new h2d.filter.Blur(1, 1),
    new h2d.filter.Glow(0x00FF00, 0.5, 5)
]);

Component Pattern

Component Interface

interface IComponent {
    function update(dt:Float):Void;
    function onAdd(entity:Entity):Void;
    function onRemove():Void;
}

class Entity extends h2d.Object {
    var components : Array<IComponent> = [];

    public function addComponent(comp:IComponent) {
        components.push(comp);
        comp.onAdd(this);
    }

    public function removeComponent(comp:IComponent) {
        components.remove(comp);
        comp.onRemove();
    }

    public function getComponent<T:IComponent>(type:Class<T>) : T {
        for (comp in components) {
            if (Std.isOfType(comp, type)) {
                return cast comp;
            }
        }
        return null;
    }

    override function update(dt:Float) {
        for (comp in components) {
            comp.update(dt);
        }
    }
}

Example Components

// Health component
class Health implements IComponent {
    public var current : Int;
    public var max : Int;
    var entity : Entity;

    public function new(max:Int) {
        this.max = max;
        this.current = max;
    }

    public function onAdd(entity:Entity) {
        this.entity = entity;
    }

    public function takeDamage(amount:Int) {
        current -= amount;
        if (current <= 0) {
            entity.destroy();
        }
    }

    public function update(dt:Float) {}
    public function onRemove() {}
}

// Movement component
class Movement implements IComponent {
    public var speed = 200.0;
    public var velocity : h2d.col.Point;
    var entity : Entity;

    public function new() {
        velocity = new h2d.col.Point();
    }

    public function onAdd(entity:Entity) {
        this.entity = entity;
    }

    public function update(dt:Float) {
        entity.x += velocity.x * dt;
        entity.y += velocity.y * dt;

        // Friction
        velocity.x *= 0.9;
        velocity.y *= 0.9;
    }

    public function onRemove() {}
}

Collision Detection

Simple Bounds

class CollidableEntity extends Entity {
    public var bounds : h2d.col.Bounds;

    public function new() {
        super();
        bounds = new h2d.col.Bounds();
    }

    public function checkCollision(other:CollidableEntity) : Bool {
        return bounds.intersects(other.bounds);
    }

    public function updateBounds() {
        bounds.x = x - bounds.width * 0.5;
        bounds.y = y - bounds.height * 0.5;
    }
}

Collision Groups

class CollisionWorld {
    var groups : Map<String, Array<CollidableEntity>> = [];

    public function addToGroup(entity:CollidableEntity, group:String) {
        if (!groups.exists(group)) {
            groups.set(group, []);
        }
        groups.get(group).push(entity);
    }

    public function checkCollisions(group1:String, group2:String, 
                                   callback:CollidableEntity->CollidableEntity->Void) {
        var g1 = groups.get(group1);
        var g2 = groups.get(group2);

        if (g1 == null || g2 == null) return;

        for (e1 in g1) {
            for (e2 in g2) {
                if (e1.checkCollision(e2)) {
                    callback(e1, e2);
                }
            }
        }
    }
}

// Usage
var collisions = new CollisionWorld();
collisions.addToGroup(player, "player");
collisions.addToGroup(enemy, "enemies");

collisions.checkCollisions("player", "enemies", (p, e) -> {
    p.takeDamage(10);
    e.knockback(p.x, p.y);
});

Object Pooling

Generic Pool

class ObjectPool<T:h2d.Object> {
    var available : Array<T> = [];
    var active : Array<T> = [];
    var create : Void->T;
    var parent : h2d.Object;

    public function new(create:Void->T, parent:h2d.Object, initialSize=10) {
        this.create = create;
        this.parent = parent;

        // Pre-fill pool
        for (i in 0...initialSize) {
            var obj = create();
            obj.visible = false;
            available.push(obj);
        }
    }

    public function get() : T {
        var obj = available.pop();
        if (obj == null) {
            obj = create();
        }

        parent.addChild(obj);
        obj.visible = true;
        active.push(obj);
        return obj;
    }

    public function put(obj:T) {
        active.remove(obj);
        obj.visible = false;
        obj.remove();
        available.push(obj);
    }

    public function clear() {
        for (obj in active.copy()) {
            put(obj);
        }
    }
}

// Usage
var bulletPool = new ObjectPool(
    () -> new Bullet(),
    gameLayer,
    50
);

// Spawn bullet
var bullet = bulletPool.get();
bullet.reset(x, y, angle);

// Return to pool
bulletPool.put(bullet);

Layer Management

Using h2d.Layers

class GameScene extends h2d.Layers {
    public static inline var LAYER_BG = 0;
    public static inline var LAYER_WORLD = 1;
    public static inline var LAYER_ENTITIES = 2;
    public static inline var LAYER_EFFECTS = 3;
    public static inline var LAYER_UI = 4;

    public function new() {
        super();

        // Ensure layers exist
        for (i in 0...5) {
            add(new h2d.Object(), i);
        }
    }

    public function addBackground(obj:h2d.Object) {
        add(obj, LAYER_BG);
    }

    public function addEntity(entity:Entity) {
        add(entity, LAYER_ENTITIES);
    }

    public function addEffect(effect:h2d.Object) {
        add(effect, LAYER_EFFECTS);
    }
}

Animation

Using h2d.Anim

class AnimatedEntity extends Entity {
    var anim : h2d.Anim;
    var animations : Map<String, Array<h2d.Tile>>;

    public function new() {
        super();

        animations = new Map();
        loadAnimations();

        anim = new h2d.Anim(animations.get("idle"), 10, this);
        anim.center();
    }

    function loadAnimations() {
        var sheet = hxd.Res.sprites.character.toTile();

        animations.set("idle", sheet.gridFlatten(32, 0, 0, 4));
        animations.set("walk", sheet.gridFlatten(32, 0, 1, 8));
        animations.set("attack", sheet.gridFlatten(32, 0, 2, 6));
    }

    public function playAnimation(name:String, loop=true, onEnd:Void->Void=null) {
        var frames = animations.get(name);
        if (frames == null) return;

        anim.play(frames);
        anim.loop = loop;
        anim.onAnimEnd = onEnd;
    }
}

Best Practices

1. Entity Lifecycle

class Entity extends h2d.Object {
    public function new() {
        super();
        onCreate();
    }

    function onCreate() {
        // Initialize components
    }

    public function reset() {
        // Reset for pooling
    }

    public function destroy() {
        onDestroy();
        remove();
    }

    function onDestroy() {
        // Cleanup
    }
}

2. Update Order

class GameScene {
    function update(dt:Float) {
        // 1. Input
        player.handleInput();

        // 2. AI
        for (enemy in enemies) {
            enemy.updateAI(dt);
        }

        // 3. Physics
        for (entity in entities) {
            entity.updatePhysics(dt);
        }

        // 4. Collisions
        checkCollisions();

        // 5. Animation
        for (entity in entities) {
            entity.updateAnimation(dt);
        }
    }
}

3. Memory Efficiency

class Entity extends h2d.Object {
    // Share resources
    static var sharedTexture : h3d.mat.Texture;

    // Clean up references
    override function onRemove() {
        super.onRemove();

        // Clear references
        target = null;
        parent = null;

        // Remove listeners
        removeEventListeners();
    }
}

Next Steps

Learn about Rendering → to understand how Heaps draws your entities.