Without Entity Systems
Last week I wrote about Entity Systems that can be used as alternatives to ECS.
When I posted about this on X, a few people rightfully pointed out that you probably don’t even need to have entities.
Having an Entity is pretty generic, and chances are you know what’s going to be in your game.
If you pack everything into one type, you can end up with performance issues rather quickly.
For example, I was working on a colony simulation game and had static arrays of Tile
which encapsulated more data than required.
I found that the CPU cache was being blown out while doing calculations, causing a drop from 60 FPS down to 20.
By moving the data out into a different data structure and changing access patterns, I got the FPS back to 60.
This was just a prototype, so I didn’t do proper profiling - In a real game I’d suggest using “Frame Time” and/or more discrete timers rather than FPS
The point is, I was surprised at how quickly this stuff matters.
Of course, it largely depends on the type of game you are making. Colony/simulation games are notoriously CPU intensive - that’s why they all have a tick-rate and don’t run the simulation at the same speed as rendering.
In a platforming game I was working on, there was no concept of Entities.
We had spikes, enemies, rooms, doors, tiles, etc. And these all had their own types.
Player :: struct {
hit_box: Rect,
vel: Vec2,
// ...
}
Spike :: struct {
hit_box: Rect,
facing: Direction,
}
Door :: struct {
hit_box: Rect,
to: string,
}
Room :: struct {
doors: []Door,
tiles: []Tile,
spikes: []Spike,
enemies: []Enemy,
// ...
}
Enemy :: struct {
pos: Vec2,
vel: Vec2,
hit_box: Rect,
behaviors: bit_set[Enemy_Behavior],
// ...
}
Game_State :: struct {
current_room: string,
rooms: map[string]Room,
// ...
}
This is just an example, there are many ways to structure the data depending on your game.
When writing the behavior code for all of this, I iterated through any type that needs an update.
update_player :: proc(player: ^Player, room: ^Room) {
// Move player based on input
for door in room.doors {
if rect_overlap(door.hit_box, player.hit_box) {
// Do room transition
}
}
for spike in room.spikes {
if rect_overlap(spike.hit_box, player.hit_box) {
if /* player traveling into pointy side */ {
// Hurt player, move to previous position
}
}
}
}
update_enemies :: proc(enemies: []Enemy) {
for &enemy in enemies {
// do stuff
}
}
The way you write the code-paths will vary based on your game design.
Maybe that seems like a bad thing. You are always coming up with new code…
But what I’ve found when I try to write extensible code from the get-go is that it:
- Never gets extended, so it’s wasted dev time
- Gets extended but the requirements are different to the way I thought, so I have to rewrite it anyway
It’s not often that I’ve written code that I can just copy and paste into a new project. It does happen, but the majority of the time I’m writing project specific code.
So, yes, you may not even need an Entity system.
I’m sure there are many other ways to structure game code.
As I discover more about these game programming topics, I’ll continue writing about them - so, sign up for free today to get future issues.
Thank you for reading! If you know anyone who may benefit from from reading this, please share it with them.
Cheers, Dylan