The Purity Spiral

Alexander Avery

Fri | Jun 3, 2022

Lucky bamboo plant formed in a spiral

Starting development three times

I’ve started this project three times and I don’t intend to start it a fourth. For years, I’ve wanted to develop my next game with an ECS, and I had my sights set on a few existing engines. Since I primarily use Go, my first attempt was with Engo.

It’s a fine engine and has a lot of existing, reusable systems. About thirty minutes in, I had my first Tiled map rendered, set up a player, and set up a camera to follow it. Unfortunately, it was equally quickly that I found my first hurdle. In the gif below, you can see that moving the camera caused lines to appear between the blue wall tiles.

Splitting tiles seen as camera moves

These splits between tiles appeared in a lot of other areas as well. No matter where, these splits were in the same spots and showed the same colors. After noticing there were no other reports of these issues, I checked the tile set used in the example project.

Sure enough, the example tile set had a one pixel gap between each tile. The tile set I was using had no such gap, and I was sure the default shader was expecting them. Making a working prototype was the priority, so I decided to ignore the issue until I had something playable. That was the plan, anyway.

Making Collisions

The next order of business was to set up collisions between the player and the world. The promoted library to use for Engo is Box2D.go. As you’d expect, this is a straightforward port of Box2D to Go.

So, I set off implementing collisions based on example projects that use Box2D.go and Engo. It seemed like a cakewalk since there is already a box2d Engo system, yet the depth of features box2d provides quickly became evident.

As needed for a physics simulation, Box2d allows you to define numerous physical states. B2BodyDefs require a mountain of values. You can specify “type”, linear velocity, position, angular velocity, angular damping or enable bullet physics… Box2D is a fine library, but was starting to appear to be overkill for what this game requires.

I soon found Box2D even had its own Vec2 implementation, then I really started to rethink my position. Engo already has its own Point type, and regular conversions between the two felt cumbersome. It was starting to look like I would have to structure my whole game around Box2d if I wanted to use it at all. The final push to make me change gears was the UserData interface{}, provided to store information inside your B2BodyDef.

All I need is to detect collisions between the player and a few boundaries. There will be no rotations, damping, or even mass. This game isn’t physics-based, and in my opinion the engine shouldn’t revolve around physics either.

Minimal Viable Product

Where do we go from here? I could make a collision system, and I will one day, but I’d hate for it to get in the way of my prototype. So for my next trick, I will make my prototype without any collisions at all. Who needs them (yet)? This game is about serving Milk Tea and making stacks of cash, not about proving Pandas can’t walk through walls!

The final game will have a collision system, and I’m psyched to learn how to implement one efficiently. But, for now, we’re going to just walk through walls and serve some tea.

Starting from Scratch

Upon discovering the tile rendering issue, you may remember I decided to ignore problems until completion of the prototype. After spending days trying to learn Box2D with the same mindset, I realized something that changes that. It took hours grappling with libraries to use them how I like, when I intend to replace them anyway. There is lots to learn by studying existing libraries, but that distracts from producing a prototype quickly.

Between the ready-made collision system, and issues with rendering tiles, there is a commonality. There is simply too much provided by these libraries for me to use them effectively in the short term. If I’m starting fresh after the prototype anyway, I want to learn the libraries that will make it into the final rendition.

Ebiten and ECS

Ebiten is one of the most popular game engines for Go. I had heard about it many years ago, but didn’t consider it for this project because I was too focused on the “ECS branding”. As it turns out, Ebiten only deals with audio, input, and drawing to the screen, declaring nothing about how your game loop should run.

From awesome-ebiten, I found several examples of frameworks using Ebiten with an ECS. None of them worked quite how I wanted, but it gave me hope that I could create my own. After searching for other ECS implementations in Go and trying them, I settled for go-gameengine-ecs.

My final switch came only after I had a basic example of rendering with Ebiten and an ECS. The cause of said switch was usability of shaders. Ebiten ships with its own shader language, Kage, that compiles to OpenGL and Metal. This is a fantastic idea that I want to learn more about, but for the time being it is lacking something that I want.

The current dealbreaker is that you must use Kage for all Ebiten shaders. Perhaps one day you can write shaders directly in GLSL or other shading languages, but for now, you cannot. So one last time, before I got too productive, I swapped Ebiten for raylib-go.

The Pursuit of Pluggability

Raylib is such a popular game library that everyone made a wrapper in their favorite language and forgot to have a flame war. The API is simple but allows you to get off the ground quickly, and I was beyond impressed.

Within a few minutes, I was able to render the tile set in a window. One hour later I was loading my tilemap with go-tiled, and the next hour I had that rendering in the window too. I was also able to experiment a bit with the GPU textures. The current version renders the map tiles to a texture at “Preload”, then renders that texture to the screen once per tick.

To catch you up on the remaining progress:

  1. We can load individual sprites from a sprite sheet and render them.
  2. Player movement is incorporated into the ECS.
  3. Rendering textures is incorporated into the ECS.
  4. The camera follows the player every tick in the render step.
  5. Tiles no longer split along edges!
Amongus character accidentally rendered to size of full sprite sheet in raylib

No matter what I’m using now, my goal is for the libraries I pick to mingle with each other as little as possible. It’s likely that in the future I will swap some library out, so I want to make sure the core of my game cares as little about libraries as possible.

If I later find the cgo overhead is too much for all my Vec2 calculations, I should be able to escape with a little gofmt magic. Any more effort than that could be a critical waste of time, and I think we can all agree it’s time to focus on prototyping gameplay now. For clarity’s sake, this game may never reach a point where cgo is a relevant bottleneck, for now, it’s just an example.

Thank you all for reading along with the latest developments! I’ll be posting a video version of this devlog to Odysee soon. Be sure to follow if you want to see the latest footage and get a closer look at the challenges along the way.

>> Home