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 wouldn't say game dev is making me LIKE object oriented programming, but it is making me accept it has a (limited) use case
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) ๐
I've never really been convinced about object oriented programming but doing my little game dev hobby has finally shown me one counterpoint that I can get on board with. Makes sense when the domain is literally "objects in a game world" lol.
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).
Entity Component System (ECS) brings composability to game development ๐ช Split data and logic, and just compose entities with what they need to operate Clean, composable and powerful ๐ฅ
Meanwhile, my newest and latest project is out now: Local-only calories tracker app ๐
New project landing next week Tuesday 26 November ๐ Local-only calories tracker app ๐๏ธ @EffectTS_ โก๏ธ PGlite with live queries @ElectricSQL ๐ @DrizzleORM ๐ Actors with XState @statelyai ๐งฑ @tan_stack router Your next local-first stack ๐
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 ๐