Entity component system

Alternative to object oriented application design

Entities.

Components.

Systems.

Entities

  • No data, no behavior, just an identifier
  • Item in a pool (game)
  • An ID and a collection of components

Components

  • Just data, no behavior, no identifier
  • Attached to entities (the entity's "properties")

Systems

  • No data, just behavior, no identifier
  • Works with entities and their components
  • Deterministic (fingers crossed)

Entities and their components

Jan-Oliver

JSS

What about systems?

Each piece has a unique ID

"Black Queen" and "White King"

"Piece" component

Properties:
- Color
- Type (King, Queen, ...)

"Position" component

Properties:
- Row (A to H)
- Column (1 to 8)

Enough talking... show me code.

var pool = new Pool();

pool.createSystem(RUN_MODE.INIT, "CreateGrid", () => {

    pool.createEntity("Grid")
        .addComponent("grid", { rows: 8, columns: 8 });

});

pool.createSystem(RUN_MODE.INIT, "CreatePieces", () => {

    pool.createEntity("Black Queen")
        .addComponent("piece", { color: "b", type: QUEEN })
        .addComponent("position", { row: "D", column: 8 });
    
    pool.createEntity("Black King")
        .addComponent("piece", { color: "b", type: KING })
        .addComponent("position", { row: "D", column: 1 });

    /* ... */
});

pool.createSystem(RUN_MODE.FRAME, "RenderGrid", () => {

    var gridEntity = pool.getEntities(["grid"]).first();

    gameEngine.renderGrid(
        gridEntity.grid.rows,
        gridEntity.grid.columns
    );

});

pool.createSystem(RUN_MODE.FRAME, "RenderPieces", () => {

    var entities = pool.getEntities(["piece", "position"]);

    for (entity in entities) {
        
        gameEngine.renderPiece(
            entity.piece.color,
            entity.piece.type,
            entity.position.row,
            entity.position.column
        );

    }

});

Time to move some pieces

pool.createSystem(RUN_MODE.FRAME, "MovePiece", () => {

    var entities = pool.getEntities(["piece", "position"]);

    var input = gameEngine.getMoveInput();

    var selectedPiece = entities.filter(entity => 
        entity.position.row == input.old_row &&
        entity.position.column == input.old_column
    );

    selectedPiece.replaceComponent("position", {
        row: input.new_row, column: input.new_column
    });
});

Input:
E7 -> E5

What is missing?

  • Only allow players to move alternately
  • Allow to remove opponent's pieces
  • Implement piece type specific movement rules
  • ...

NA-0002: Only allow players to move alternately 

Time to put some validation into the "MovePiece"-System

Better implement a system for validation

pool.createSystem(RUN_MODE.FRAME, "MovementIntent", () => {

    var grid = pool.getEntities(["grid"]).first();

    var nextMove = "w";
    if (
        grid.hasComponent("lastMove")
        && grid.lastMove.color == "w"
    ) {
        nextMove = "b";
    }

/*...*/

    var entities = pool.getEntities(["piece", "position"]);

    var input = gameEngine.getMoveInput();

    var selectedPiece = entities.filter(entity => 
        entity.position.row == input.old_row &&
        entity.position.column == input.old_column
    );

    if (selectedPiece.piece.color != nextMove) {
        return gameEngine.error(
            "The next move belongs to: " + nextMove
        );
    }

    selectedPiece.addComponent("movementIntent", {
        row: input.new_row, column: input.new_column
    });

    grid.replaceComponent("lastMove", { color: nextMove });
});

A little change to the move system

pool.createSystem(RUN_MODE.FRAME, "MovePiece", () => {

    var entities = pool.getEntities(
        ["moveIntent", "position"]
    );

    for (entity in entities) {

        var intent = entity.moveIntent;

        entity.position.row = intent.row;
        entity.position.column = intent.column;
        entity.removeComponent("moveIntent");

    }
});

NA-0003: Allow to remove opponent's pieces 

pool.createSystem(RUN_MODE.FRAME, "RemovePiece", () => {

    var entities = pool.getEntities(
        ["piece", "position", "lastMoveRound"]
    );

    foreach (entity in entities) {

        var entityOnSameField = entities.filter(other =>
            other.id != entity.id
            && other.position == entity.position
            && other.lastMoveRound < entity.lastMoveRound
        ).first();
        
        if (entityOnSameField != null) {
            
            pool.removeEntity(entityOnSameField.id);

        }
    }
});

Recognize something?

1 feature = 1 system

Do we have time left?

Maybe you understand GO's composition better now :)

Imagine the following

var ecomOrder = pool.createEntity(uuid4())
    .addComponent("authorizedPayment", {
        psp: "stripe",
        charge_id: "..."
    })
    .addComponent("shipable", {
        address: { /* ... */ },
        carrier: "DHL",
        method: "Express"
    });

Time to create 2 systems

pool.createSystem("order.placed", "DeliverySystem", () => {
    
    var orders = pool.getEntities(
        ["shipable"], ["bookedShipment"]
    );

    for (order in orders) {
        
        var bookedShipment = callCarrierApiToBookShipment(
            order.shipable.carrier,
            order.shipable.method,
            order.shippable.address
        );

        order.addComponent(
            "bookedShipment", { shipment: bookedShipment }
        );

    }

});
pool.createSystem("order.shipped", "PaymentCapture", () => {
    
    var orders = pool.getEntities(
        ["authorizedPayment"], ["capturedPayment"]
    );

    for (order in orders) {
        
        var capture = capturePayment(
            order.authorizedPayment.psp,
            order.authorizedPayment.charge_id
        );

        order.addComponent(
            "capturedPayment", { payment: payment }
        );

    }

});

Need moar?

var instoreOrder = pool.createEntity(uuid4())
    .addComponent("capturedPayment", {
        payment: paymentFromMiuraDevice
    });

The systems for the ecom order don't even recognize the in store order

Entity component system

By Jan Pantel

Entity component system

  • 283