β€’

tech

Game dev, functional programming, and Entity Component System

Is there such a thing as functional programming game dev? I explored how you would go about making a game with FP. It didn't work that well. But then ECS came along...


Sandro Maglione

Sandro Maglione

Software

I do FP. Close to all I do is FP. And then there is game dev. Is game dev FP friendly? πŸ€”

I explored how you would go about making a game with FP. It's not working that well.

Let me explain you πŸ‘€


Is game dev the ultimate OOP validation?

OOP seems to be the tool for game dev:

I made many games in the past, all with OOP (no framework available otherwise):

  • Android games with Android Studio and Java 🫠
  • Unity games with C# πŸ—οΈ
  • Godot games with GDScript πŸ”—
  • Flutter games with Dart (flame) πŸ’™

All were a tangled mess of classes that referenced and mutated other classes.

Even since I joined the FP-side of coding, this classes-thing gives me tingles, as if something is wrong, but works.

I read this referenced as the "Blob anti-pattern" (aka God Objects) πŸ˜…

If you are interested you can read about some of these projects:

FP in games, anyone?

The thing is: there is not much talk about FP in games.

Even imagining an FP architecture in games requires a radical shift, such that it may not be worth it.

I explored some ideas this week, in code. Here is the gist of it:

  • Use data instead of objects ("no classes")
  • Input is just data as well
  • Immutable and scoped updates (no mutations)
export type Vector2D = Readonly<{ x: number; y: number }>;

export type EntityId = number;

export type Component<Label extends string> = Readonly<{
  component: Label;
}>;

export type Entity<Label extends string> = Readonly<{
  entity: Label;
  components: ReadonlyArray<Component<any>>;
}>;

export type GameEntity = PlayerEntity | ObstacleEntity;

export type GameState = Readonly<{
  entities: ReadonlyMap<EntityId, GameEntity>;
  time: number;
}>;

The game state is a function of input and previous state:

export const updateGame = (state: GameState, input: InputState): GameState => {
  const updatedEntities = new Map(state.entities);

  for (const [entity, component] of state.entities) {
    updatedEntities.set(
      entity,
      Match.value(component).pipe(
        Match.when({ entity: "player" }, Player.update),
        Match.when({ entity: "obstacle" }, Obstacle.update),
        Match.exhaustive
      )(input)
    );
  }

  return create(state, (draft) => {
    draft.entities = updatedEntities;
    draft.time = state.time + 1;
  });
};

But then things get tricky. How do you create a components' system? How do you implement references between data?

Furthermore, I have been trying to integrate this model with PixiJS, which is a pure mutable OOP-based library. It's hard to translate immutable data to mutable, while taking care of performance.

export const syncPixiState = (
  gameState: GameState,
  pixiState: PixiState
): void => {
  for (const [entity, component] of gameState.entities) {
    let sprite = pixiState.sprites.get(entity);

    if (!sprite) {
      // Create new sprite
      sprite = new Sprite(Texture.from(component.texture));
      pixiState.stage.addChild(sprite);
      pixiState.sprites.set(entity, sprite);
    }

    entityRenderer(component, sprite);
  }

  // Remove sprites for entities that no longer exist
  for (const [entity, sprite] of pixiState.sprites) {
    if (!gameState.entities.has(entity)) {
      pixiState.stage.removeChild(sprite);
      pixiState.sprites.delete(entity);
    }
  }
};

I didn't come to a definite conclusion (yet), and I am actually exploring other options to keep some sanity even without FP-purism.

Entity Component System (ECS)

And then I came across Entity Component System 🀯

ECS is a pattern for game development that separates components (data), entities ("things" in the world), and systems (logic/implementation).

It's basically composability in game dev πŸ™Œ

Traditionally you would think as a "player" object or "enemy" object, and attach all behaviors to them.

In ECS each entity ("player"/"enemy") it's a composition of components ("motion"/"physics"/"input").

A system is then responsible for applying a group of components to entities:

  • System A = "motion" + "input" (the input updates the motion)
  • System B = "physics" + "motion" (apply physics forces based on motion)

I also learned that Unity provides this system as an alternative to GameObjects (ECS for Unity).

I see a path forward to make this FP-based as well. I plan to explore this more equipped with this new knowledge (pitfalls may apply).


Meanwhile, my newest and latest project is out now: Local-only calories tracker app πŸš€

The world doesn't stop, and React Router v7 and TanStack Start Beta are out as well. No excuses for even more exploration 🫑

See you next πŸ‘‹

Start here.

Every week I dive headfirst into a topic, uncovering every hidden nook and shadow, to deliver you the most interesting insights

Not convinced? Well, let me tell you more about it