Home

Awesome

edge

Entity system for Haxe

introduction

edge works on the following principles:

install

For the official release:

haxelib install edge

For the cutting-edge/dev-version:

haxelib git edge https://github.com/fponticelli/edge.git

example

In the example below we create a bunch of entities some with both Position and Velocity and some with only Position. The system UpdateMovement will only affect the entities with both components, while RenderingDots will be applied to all the entities in this context.

RenderingBackground will clear the canvas on every frame before any other rendering operation. Note that it will only be invoked once per frame and it doesn't rely on the presence of any entity (update() takes no argument).

See the example in action.

import edge.*;
import minicanvas.MiniCanvas;

class Game {
  public static var width(default, null) = 200;
  public static var height(default, null) = 200;

  public static function main() {
    var mini = MiniCanvas.create(width, height)
                 .display("basic example"),
        world = new World();

    for(i in 0...300)
      world.engine.create([
          new Position(
            Math.random() * width,
            Math.random() * height),
          new Velocity(
            Math.random() * 2 - 1,
            Math.random() * 2 - 1)
        ]);

    for(i in 0...20)
      world.engine.create([
          new Position(
            Math.random() * width,
            Math.random() * height)
        ]);

    world.physics.add(new UpdateMovement());

    world.render.add(new RenderDots(mini));

    world.start();
  }
}

class Position implements IComponent {
  var x : Float;
  var y : Float;
}

class Velocity implements IComponent {
  var vx : Float;
  var vy : Float;
}

class RenderDots implements ISystem {
  var mini : MiniCanvas;
  public function new(mini : MiniCanvas)
    this.mini = mini;

  function before()
    mini.clear();

  function update(pos : Position)
    mini.dot(pos.x, pos.y, 2, 0x000000FF);
}

class UpdateMovement implements ISystem {
  function update(pos : Position, vel : Velocity) {
    var dx = pos.x + vel.vx,
        dy = pos.y + vel.vy;
    if(dx <= 0 && vel.vx < 0 || dx >= Game.width && vel.vx > 0)
      vel.vx = -vel.vx;
    else
      pos.x = dx;
    if(dy <= 0 && vel.vy < 0 || dy >= Game.height && vel.vy > 0)
      vel.vy = -vel.vy;
    else
      pos.y = dy;
  }
}

ISystem

Systems must implement ISystem. System classes do not have to implement the required __process__ because this field is automatically built.

A system must at least define a method update(). The method can take 0 or more arguments. If arguments are defined, they must be components; a component is an instance of any Class<Dynamic>.

The update() method will be invoked when the corresponding phase is updated. update() will be called only once if it takes no arguments, or once for every entity that matches the function requirements. An entity matches the update requirements if it has a matching component for each of the function arguments.

Optionally a System can expose the following members:

If the System exposes any of these members, they will be automatically populated at the right time. So no initialization is required or desired. Also they will be automatically changed to public if they are not already.

Sometimes you want to be able to iterate over collections of entities that satisfy certain requirements. For example, it can be extremely useful for collisions. In that case you can define one (or more) fields of type edge.View(T). Where T is the type of an anonymous object where each field must be have the type of a component.

var targets : View<{ position : Position, life : Life }>;
var entity : Entity;

function update(position : Position, bullet : Bullet) {
  for(item in targets) {
    var target = item.data; // item.data stores the components
    // hit the target?
    if(areNear(position, target.position)) {
      // assign damage
      life.hitPoints -= bullet.damage;

      // remove bullet
      entity.destroy();

      // life is zero remove target
      if(life <= 0)
        item.entity.destroy(); // item.entity references the target entity
    }
  }
}

System can also receive notifications when an entity has been added or removed from a View.

From the example above, if you want to perform a special operation when a new target is paired with your system you can define the following method:

function targetsAdded(entity : Entity, data { position : Position, life : Life }) {
  // do something with the newly added entity/components
}

The magic here is in the name of the method that needs to follow the format:

${viewFieldName}Added

The same signature with Removed can be used to define a method that does some cleanup when an entity is unpaired from a view.

A special View is created for the method update called updateItems. For that you can define either, both or neither of updateAdded/updateRemoved methods.

For this update function:

function update(position : Position, bullet : Bullet)

The added/removed methods will look like:

function updateAdded(entity : Entity, data : { position : Position, bullet : Bullet }) {}

function updateRemoved(entity : Entity, data : { position : Position, bullet : Bullet }) {}

IComponent

Even if not required, your components can implement IComponent. Doing so your components will gain the following super-powers for free: