I’m writing a new game engine in Zig, and I’m open sourcing each module as I write it.
Today I released the beta version of my entity component system ZCS, I’ll consider it a beta until I’ve shipped my next Steam game using it as these things tend to evolve with usage.
Here’s a quick sample:
// Reserve space for the game objects and for a command buffer.
// ZCS doesn't allocate any memory after initialization.
var es: Entities = try .init(.{ .gpa = gpa });
defer es.deinit(gpa);
var cb = try CmdBuf.init(.{
.name = "cb",
.gpa = gpa,
.es = &es,
});
defer cb.deinit(gpa, &es);
// Create an entity and associate some component data with it.
// We could do this directly, but instead we're demonstrating the
// command buffer API since it's used more frequently in practice.
const e: Entity = .reserve(&cb);
e.add(&cb, Transform, .{});
e.add(&cb, Node, .{});
// Execute the command buffer
// We're using a helper from the `transform` extension here instead of
// executing it directly. This is part of ZCS's support for command
// buffer extensions, we'll touch more on this later.
Transform.Exec.immediate(&es, &cb);
// Iterate over entities that contain both transform and node
var iter = es.iterator(struct {
transform: *Transform,
node: *Node,
});
while (iter.next(&es)) |vw| {
// You can operate on `vw.transform.*` and `vw.node.*` here!
}
I’ve written a number of ECSs in the past including the one used by Magic Poser. Going through this process a few times has given me a chance to hone in on what I want out of an ECS and I’m really happy with how this one turned out.
In particular, I think my command buffer extension implementation is an interesting solution to a problem I’ve experienced with other ECS implementations.
If you have any questions or feedback let me know!
I’ll be shifting my focus to my render API abstraction & renderer next, if you want to keep up to date with what I’m working on consider signing up for my mailing list.
25 Likes
Awesome! Thanks for open-sourcing parts of your Zig game engine.
Looking forward to the renderer abstraction as well. Btw, are you going to use SDL for it?
P.S. I’m going to study the write-up as soon as I’m done watching Andrew’s stream 
2 Likes
You’re welcome! Sharing my work has been fun—and getting the occasional PR has been great.
I’m undecided how I’m going to handle windowing. Whatever I decide, the renderer won’t be coupled to it—you’ll be able to bring your own.
I have GLFW ported to the Zig build system here with support for cross compilation out of the box, cross compilation is important to me because I ship for both Windows and Linux and don’t want to reboot into my other partition to make builds.
While that’s been working fine, there are parts of it I’m not happy with and I’m considering writing my own window management code in the long run. I did this for Way of Rhea so I have a starting point I could copy paste from, but this platform stuff always eats up a lot of time regardless so I’m not sure if I can justify the time it’ll take yet or not.
4 Likes
Looks very interesting, thanks for sharing.
I also working on an open source ECS and general component system for 2D game programming in Zig called Firefly-Zig, but it is probably more coupled then ZCS and comes with its own backend that uses raylib. Raylib was very easy and convenient to integrate and I can recommend it.
2 Likes
You’re welcome!
And nice I’ll check it out! I’m always interested to see how other people handle the component registration/type ID problem and people’s strategies for accelerating querying for archetypes.
I’m definitely going for a very modular approach—even the Transform and Node components are under the “extensions” namespace, you can ignore them if you’re doing your own thing.
I didn’t discuss it in the writeup, but I have two main motivations for this approach:
- I think I can personally provide more value to the open source ecosystem this way. I think it’ll be easier for people to justify adopting a few of my libraries that they find useful than to have to accept being fully locked in on my tech for a game they might spend a year+ working on.
- I went too far in the other direction with Way of Rhea, and as a result now that my vision for my engine changed I’m stuck starting from scratch instead of just replacing the parts I wanted to upgrade. I’m trying to avoid that this time around, I want to be able to build all my future games on this tech.

I’ve heard a lot of good things about Raylib!
I’m taking a bit of a different approach—I have a GPU API abstraction library that currently supports Vulkan as a backend, but is architected with a possible DX12 and console backends in mind. It’s focused around modern bindless rendering techniques since they’re faster and while also IMO being much easier to work with.
Once I get that to a publishable state I’m going to build a 2D renderer on top of it with a focus on hardware antialiasing scaled 2D sprites correctly, since that’s a tricky subject that’s relevant to my next game that I don’t often see handled well.
8 Likes
I see, this makes sense to me.
Zig is my first low level language and I come from object oriented languages like Java or Kotlin. But I really felt in love with Zig and just ported my Kotlin based 2D game API to Zig now. This definitely had an impact and let my OO thinking leaking into the current port in Zig. Now that I understand Zig a bit better, I would do some things differently. But I want first create a minimal Game from what I have now.
I’m always interested to see how other people handle the component registration/type ID problem and people’s strategies for accelerating querying for archetypes.
Sorry, my English is not as good as I would like it is, but I will try to explain the basics here. Hope it is somehow clear and make sense.
In “firefly” I heavily make use of DynArray(T) and Bitset. DynArray is a dynamic array that grows if needed to store the components by index. So the Array is allocated in junks and only when needed or can be pre-allocated of arbitrary size. The active slots/indexes of a DynArray are hold within a BitSet. One can iterate the BitSet of a DynArray, to get the indices of active components. To deactivate/delete a component, one can just reset the bit on the respective index of the component. If a new component is created the next free index from the DynArray is used and the component data is stored within the array (no allocation here) and the bit on index is set.
I also associate ids/indices with component types and use BitSet for entity archetypes. This is called “Kind” in “firefly”. An entity has barely an id/index and a Kind. The Kind tells you what types EntityComponent the Entity has and is just a BitSet. Bit-operations are used to match entities of needed kinds when iterating over all active entities for example.
Entity.Component.process(
Kind{ Transform, Sprite },
render,
);
fn render(id: index) void {
renderer.RenderTexture(
Transform.Component.byId(id),
Sprite.Component.byId(id),
);
}
Or one can subscribe to Entity with a certain Kind filter to get notified on Entity activation/deactivation of interested kind and hold a BitSet with all Entity indices of interest and just only iterate the BitSet on every frame to process the entities. Most renderer do it this way.
I’m sure there are better and more performant ways to do this but it fits for my needs for now 
2 Likes
I see, this makes sense to me.
Zig is my first low level language and I come from object oriented languages like Java or Kotlin. But I really felt in love with Zig and just ported my Kotlin based 2D game API to Zig now. This definitely had an impact and let my OO thinking leaking into the current port in Zig. Now that I understand Zig a bit better, I would do some things differently. But I want first create a minimal Game from what I have now.
That seems like a good approach to me! Great work–especially for your first time with a lower level language.
In case it was unclear, I wasn’t suggesting that my modular approach is the only good way to do things, I’m just just elaborating on my motivations.
Sorry, my English is not as good as I would like it is, but I will try to explain the basics here. Hope it is somehow clear and make sense. […]
Nice, this makes sense!
In ZCS I generate a type ID for each type at compile time like this.
These IDs aren’t densely packed though, so on first use as a component each type ID is given a densely packed index for use in bitsets.
The actual component data is stored in chunk lists where each chunk in the list has the same archetype. When you want to iterate over a given archetype, ZCS only has to check the bitset in the head of each archetype list to figure out whether it should iterate that list or skip it entirely.
So far I’ve found this to be very fast in practice–there are typically a large number of entities, and a medium number of unique archetypes. That being said I think there’s probably a more clever way to do this with some sort of tri based structure that would allow me to skip even considering most of the archetype lists.
4 Likes