I Developed a 3D Game in Rust with Amethyst

First things first… why?

The moment I started using rust, I fell in love with the language. Since then, I have written a small 3D game engine of my own, before discovering Amethyst. I was aware of Amethyst’s infancy, but after having a look at the rendering code etc. I decided that I would make it work.

The Game Itself

The game (called ‘Gravity’) is a 3D turn based action game set in space, where the goal is to retake the solar system from the ‘Enemy AI’. You play as the ‘Avenger’ which can spawn small drones used for combat. At this point, the strategy/balencing is not good, but I think there’s potential.

The complete source code of the game, including assets, can be found here:

Using SPECS

Prior to this game, I had never used an ECS before, and composing my entities correctly was a challenge, especially in a turn based game. In a turn based game, there is a lot of ‘global state’ stuff, and so managing this while splitting all the logic between systems was difficult. For example, imagine we’re writing the logic for the enemy attack cycle. How do we:

  1. Know when to begin the enemy attack cycle.
  2. Correctly process the AI for each ability, when each ability is ‘self-contained’.
  3. Invoke the correct ability.
  4. Determine when the ability is over, and move on to the next enemy.

Well, if we were not in the confines of an ECS this would be simple, as we could just perform all of this in one sequential process, with all the data at hand. However, using an ECS, this is not as trivial as it may seem. Due to the way the event system works, we need to wait a frame before any of the events are actually processed. There is no way for other systems to know if an event has been triggered, which means that another system might also trigger an event which should not trigger if the first event is triggered. In turn based games, we usually only want one thing to be happening at once, which can be a problem. For example, imagine one system triggers an ability, and another is responsible for adding charge at the start of a turn. Only one can should happen at once, however, the charge event will trigger, and because there is no way for the ability event to know this, it will also trigger. Thus, both events, which should be exclusive, trigger simultaneously. To get around this shortcoming, I used placed a component on the top level entity called Principal which was populated with system and entity data of the current exclusive process. When the principal is engaged, any other attempt to engage it will fail, thus causing the ability trigger to come after the charge, since the charge would engage the principal.

Mistakes?

In retrospect, I made plenty of blunders, especially around the ECS. I used way to many individual components when all it did was overcomplicate things, in the hope that more systems could run in parallel. In fact, some of my systems required so many components it actually exceeded specs’ limit of 20! For anyone using an ECS, some advice: only add another component when adding new, unrelated behaviour. You very quickly become unnecessarily overwhelmed if you use too many.

The Amethyst Rendering Situation

This is where things get interesting… So, the premise behind rendy is a good one. However, as has been pointed out many times, functionality is WAY too difficult. I needed a few custom passes for Gravity, since it is a space game. First of all, came the procedural stars. Without the render pass example, it would have been near impossible. But I did manage to get the stars up and running without too many problems. Next came the sun. This should have been trivial (it’s just a billboard), but again a lot of plumbing required. Finally, the fun part - the Earth’s atmosphere. Now this took a long time, partly because there was no GLSL shaders I could find which fitted the brief. Expect a blog post about just atmospheric scattering in the future!

I Want to Help

If we can truly sort out the rendering situation once and for all, I think we will be in a good place. As identified by many, the biggest problem right now is how inaccessable it is. I believe that if we truly focus on bringing the ‘plumbing’ to the next level, then we can give devs the tools they need to expand the (rendering) engine themselves. Imagine a situation where devs can slowly add new features to the renderer, building it up peice by peice into something wonderful. Am I saying abandon rendy? No, of course not. Let’s just expand on rendy, to create a more high level, easy to use, boilerplate included rendering platform.

What’s the best way to do this?

Perhaps we could detatch the renderer from the engine completely… by making a rust rendering server. This would work similarly to how Godot does it’s rendering - the rendering server would run on it’s own thread. In effect, our renderer would be completely detached from the ECS, which means it is not inherently tied to amethyst in the slightest. This would provide the following benefits:

  1. Most importantly, games/engines which don’t use amethyst could still benifit from and contribute to the development of the renderer.
  2. Using a render server should make it easier to communicate between render passes (sharing data etc.)
Written on July 15, 2020