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