Entity Component System in TypeScript
Published: 2020-05-28 06:28:20

---
title: Entity Component System in TypeScript
published: true
date: 2020-05-26 00:00:00 UTC
tags: 
canonical_url: https://massivepixel.co/2020-05-26-ecs-ts/
---

Games can be architected in different ways, but Entity Component System, also known as ECS, is one of the more popular ones. I won’t spend too much time explaining why one should pick ECS over OOP or any other style, there are better resources out there.

In this series I will document my experience building ECS in TypeScript and why I did what I did.

To begin, we need a `Component`:

```
// All components must identify themselves.
type Component = {
  type: string;
};
```

Entities are composed of components and merely identifiers. However, I want to start with something easier:

```
type Entity = {
  id: number;
  components: Component[];
};

let nextId = 1;
function createEntity(...components: Component[]) {
  return {
    id: nextId++,
    components,
  };
}
```

So far, so good.

It’s important that components carry state and there should be no logic in either components or entities. So where is the logic? Inside `System`s.

System will receive a list of entities and process them in some way. They should have no state and merely operate on components/entities. They can read them, update components, create or remove components or even whole entities.

The simplest abstraction is as follows:

```
// something we need to supply every frame
// can be current time, current frame number, both or more
type TickInfo = number;

type System = {
  update: (tickInfo: TickInfo, entities: Entity[]) => void;
};
```

And that’s it! Let’s build something with this!

## An example

The first useful component we can think of is position:

```
class PositionComponent implements Component {
  type = 'position';
  constructor(public x: number, public y: number) {}
}

// easy way of creating position
const position = new PositionComponent(10, 10);
```

So how would we render it? Our game loop should be rather simple. Given a list of systems we call `update` on every one of them per frame.

```
const systems: System[] = [];
const entities: Entity[] = [];

function render() {
  const now = new Date().getTime();

  systems.forEach((system) => system.update(now, entities));

  requestAnimationFrame(render);
}

render();
```

Let’s create a simple rendering system:

```
function createRenderSystem(): System {
  const canvas = document.createElement('canvas');
  document.body.appendChild(canvas);
  const ctx = canvas.getContext('2d');

  return {
    update: () => {
      ctx.save();
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
      ctx.restore();

      entities.forEach((e) => {
        // so how do we get the rendering data
      });
    },
  };
}
```

So…given a list of entities we only care about a few select components. Let’s introduce a new helper function: `getComponent`:

```
function getComponent<T extends Component>(e: Entity, type: string) {
  return e.components.find((c) => c.type === type) as T;
}
```

Now we can finish our render system:

```
entities.forEach((e) => {
  const p = getComponent(e);
  ctx.fillRect(p.x, p.y, 1, 1);
});
```

Running this code yields a black rectangle on screen. Nothing exciting yet.

Source code for this code can be found at [https://github.com/tpetrina/ecs-ts-test/blob/master/examples/ecs1.ts](https://github.com/tpetrina/ecs-ts-test/blob/master/examples/ecs1.ts) and live demo at [https://ecs-ts-test.netlify.app](https://ecs-ts-test.netlify.app).