2024-08-17 15:05 # Devlog 1 - Building a Game Engine ## Intro to the project This devlog is the first in a series where I will go over my journey of writing a game engine from scratch. Keep in mind, the intended audience for these devlogs is my future self. This means I won't put much thought into grammar and format, and this will be a catalog of my raw learnings. I wanted to share this publicly in hopes of inspiring other people to go through with such a project. My goal is to build an [ECS](https://bevyengine.org/learn/quick-start/getting-started/ecs/)-based game engine inspired by [Bevy](https://bevyengine.org/). It is not intended to be a port, but rather I will borrow some concepts. Initially, I will aim for 2D rendering, then expand it to 3D rendering if time allows. I will be writing this project in Go, not because it is the most suitable language for this specific task, but mainly because I want to aim for productivity and getting it done instead of writing the most optimized engine out there. I aim to share an update every two weeks as I can only work on this project on Saturdays due to my workload. The source code for the project can be found [here](https://github.com/otanriverdi/hayal). ## Scope for Part 1 The scope for this chapter will be to implement a simple ECS that we can expand upon later. The first concept we will explore is understanding what an ECS is and what it tries to accomplish. > ECS is a software pattern that involves breaking your program up into **Entities**, **Components**, and **Systems**. **Entities** are unique "things" that are assigned groups of **Components**, which are then processed using **Systems**. The above quote is from the getting-started guide of the Bevy engine. Simply put, ECS helps us build our game systems with decoupled pieces using [composition over inheritance](https://www.youtube.com/watch?v=hxGOiiR9ZKg). The initial implementation of our ECS will be rather simple with some intended limitations because the goal is to understand the core concept and expand on it over time if needed. ## Building a simple ECS ### Entities An entity is basically a "being" in our game, which will be represented only by a unique identifier. The client of the engine will be able to attach components to our entities to establish "is a" relations to their entities. ```go type ( // EntityId is a unique identifier for a single entity. EntityId = uint32 ) type Ecs struct { freedCount uintptr freedIds [MaxEntitites]EntityId activeCount uintptr activeEntities [MaxEntitites]EntityId } ``` The basic idea in this implementation is that our entities will be auto-incrementing `uint32`s. When an entity gets freed, we will store the freed ID in a list to be reused later for a different entity. This makes our entity ID generation quite performant, but the downside is that we are limited to a maximum number of entities, which is a bit low for this first iteration. I will worry about that later. ## Components A component is simply a flat object type that has some properties. They can be attached to entities to assign some properties to them, and systems can later be built to query a selection of entities that share a group of components. ```go type ( // ComponentId is a unique identifier for a single component. ComponentId = uint16 // TypeId is a bitmask identifier for groups of components as types. TypeId = uint64 ) ``` By having individual components identified by a `uint16`, we allow ourselves to use a bitmask to create entity types that represent the group of components they hold, making it easier to query them later down the line. The downside here is that our system can only ever have a limited number of components. ```go type Ecs struct { // ... entityCount uintptr types [MaxEntitites]TypeId storage [MaxTypes][MaxComponentSize * MaxEntitites]uint8 } ``` The first thing that will probably stick out to the reader is `MaxComponentSize`. In this initial implementation, I chose to limit the size of components to a maximum to simplify our storage solution. Each component that belongs to a specific entity needs to be stored and accessible later by systems. The method I went with is a two-dimensional array of bytes where the first dimension is the specific types and the second dimension is a list of components stored as an array of bytes. Limiting the component size and the number of entities allows us to statically allocate the array, making the memory footprint of our ECS very predictable. ## Systems The initial System signature is very simple. The idea is to pass the `ecs` pointer directly to it so the client can use some public methods like `Query` to access all entities of a specific type. ```go // System is the function type that can operate on entities of certain type. type System = func(ecs *Ecs) ``` The way this is handled in Bevy is very nice due to the much stronger type system of Rust. ```rust fn greet_people(query: Query<&Name, With<Person>>) { for name in &query { println!("hello {}!", name.0); } } ``` In the future, I hope to improve upon my System signature to make it more ergonomic, like the Bevy version, but I am not sure how possible that is with Go. ## Goals for next chapter - With the current implementation, the client has to determine and keep track of the component IDs. I want to make this more ergonomic. - Improve the System signature and make system building more ergonomic for the clients. ## Conclusion Thank you for reading. If you have any recommendations or comments, feel free to reach out through my [Twitter](https://x.com/otrv45), and if you want to follow my progress, be sure to follow me.