Building a Game Engine Changed How I Think About Programming
I present to you the alpha of Concert in Dungeon. Building this game and its engine taught me A LOT. You see, the problem is that I'm self-taught. Maybe universities teach this in the first semester, and I'm just embarrassingly late to the party. But these ideas completely changed how I think about software.
- Computers are just Operations and Memory — OOP disappears after compilation
- ECS is for the CPU, not for the coder
- Collision systems: broad phase is harder than narrow phase
Computers are just Operations and Memory, OOP disappears after compilation
I eventually realized I never actually understood classes. At least, not the way a computer does.
My first language was C# in Unity scripts. Pretty quickly, I understood the idea of objects and classes. After all, dot notation looks incredibly intuitive:transform.position.x = 0;
I accepted those abstractions without questioning them.
The same went for inheritance, privacy, and other OOP concepts. But when I first saw decompiled pseudocode in IDA, I was amazed by the beauty of OOP's implementation. I don't know how to explain this feeling... an object is basically the same as an array. They're all just data. Under the hood, we aren't dealing with abstract concepts—we are strictly working with memory offsets.
Take this class, for example:
class Transform {
vec2 position;
vec2 size;
float rotation;
};
Accessing rotation is really just pointer arithmetic. The compiler already
knows the offset of every member, so it simply adds the correct number of bytes.
// Roughly what happens
char* pointer = (char*)&transform;
float* rotation = (float*)(pointer + sizeof(vec2) + sizeof(vec2));
Which eventually becomes something as boring as:
movss xmm0, dword ptr [rbp - 4]
That's it. No objects. No encapsulation. No inheritance. Just "load four bytes located sixteen bytes from the start of this memory block."
Here is how our struct is laid out on the stack frame (relative to the base pointer rbp):
[rbp - 20]: Start oftransform(holdsxandy)[rbp - 12]: Offset +8 (holdswandh)[rbp - 4]: Offset +16 (holdsrotation)
That's when I realized that dot notation is just syntax sugar. OOP exists for programmers. The CPU couldn't care less.
Though I have to admit, understanding the basics of inheritance, dependencies, and code decomposition is exactly what allowed me to actually finish my alpha engine!
Good abstractions matter. But not for algorithm speed. Abstractions just disappear long before your code reaches the processor.
This realization also made ECS suddenly make sense.
A typical game loop iterates over thousands of entities: bullets, enemies, particles, players... If every object is scattered across memory, the CPU spends half its time waiting for cache misses instead of doing useful work.
That's why nearly every serious engine eventually converges on some variation of ECS. It isn't because ECS is prettier. It's because CPUs are picky about memory layout.
ECS is for the CPU, not for the coder
The first design pattern I ever encountered was ECS (because of Unity), even before I knew about the Singleton pattern... So I tried to implement ECS by following this post. You can read it if you want to understand how to implement ECS in code. I just want to explain why I think ECS is such a cool pattern.
I always thought the debate was just about inheritance vs. composition—that making a
GoblinColliderMelee class was simply messy and unreadable.
But I eventually realized that readability wasn't the real argument.
ECS exists to prevent costly cache misses.
To understand why it works, look at how memory is laid out. In classic OOP, objects are scattered randomly throughout RAM. When your rendering system loops through them, the CPU has to jump all over memory (pointer chasing), causing lots of cache misses.
ECS solves this by ripping data out of objects and putting it into flat, contiguous arrays. Here is the difference:
Naive OOP
┌─────────────────────────────────────────────────────────────┐
│ Transform │ Sprite │ Audio │ Physics │ AI │ ... │
└─────────────────────────────────────────────────────────────┘
↑
CPU only needed this one part.
The rest of the cache line is wasted.
ECS
┌─────────────────────────────────────────────────────────────┐
│ Transform │ Transform │ Transform │ Transform │ Transform │
└─────────────────────────────────────────────────────────────┘
↑
Every byte loaded is immediately useful.
My engine currently has only five components
(transform, sprite, collider, script, camera).
With ECS, I can quickly iterate through:
all sprites to draw them;
all scripts to call script.update(dt);
all colliders to check collisions.
Collision systems: Broad phase is harder than narrow phase
I spent a lot of time trying to understand how to detect whether a bullet hits the player.
How do you know when two objects collide? That's the easy part. You solve simple geometry, like an Axis-Aligned Bounding Box (AABB) test.
But deciding which objects to compare is the real challenge. For some reason, it was incredibly hard for me to find clear information on how to build a proper, fast broad-phase system.
// Divide the visible world into grid cells.
// Broad phase (Uniform Grid)
for (Collider& collider : colliders)
{
int x = collider.position.x / CELL_SIZE;
int y = collider.position.y / CELL_SIZE;
grid[x][y].push_back(&collider);
}
// Narrow phase
for (Cell& cell : grid)
{
for (Collider* a : cell.colliders)
{
for (Collider* b : cell.colliders)
{
AABB(a, b);
}
}
}
I can also recommend this pdf "Physics - Broad phase and Narrow phases". The core idea of a Quadtree is so simple on paper:
You split the screen into 4 squares. You check which quadrant each object belongs to, and keep subdividing recursively until you have tiny squares containing only a few objects. Then, you only compare those objects.
// broad phase
class QuadTree
{
public:
void Split(Node& node, int depth)
{
if (depth >= MAX_DEPTH)
return;
node.CreateChildren();
for (Collider& collider : colliders)
{
if (AABB(node.bounds, collider))
node.Insert(collider);
}
for (Node& child : node.children)
Split(child, depth + 1);
}
};
In the end, I just hardcoded bullets to search only for the Player collider. It isn't a general broad-phase, but for my game it's the fastest possible one.
The best algorithm is the one that solves your problem, not the most generic one.
I structured this blog as if I learned OOP first, then ECS, then collision systems.
The revelations weren't linear.
They came in chunks.
Anyway, after making this game, i dont want to read books about clean code and argue what language is better. My algorithms will suck in every language.
You can try Concert in Dungeon Alpha 0.3.0 right in your browser here. Let me know what you think about the gameplay!