Backstory

I was so keen on trying ECS and learning all about it, that one day I decided I would make a simple and hopefully fun game utilizing it. I decided on making a 2D game, as Unity has seen a lot of action and has refined its tool over the years. As it was a learning project I decided to throw in everything new and experimental I could get my hands on.

I started working on the project just as ECS was going public. Along the way I had some struggles, going along with little to no documentation and ever-changing API. The fact that I used dependency injection from the start, coupled with ECS data and logic separation, lead to a relatively smooth development, despite the struggles. As development went on, I started to dream about stories and mechanics. Memories of old and beloved games started to mix, and in the end, I saw. If I decide, one day to do something more with it, it would be a mix of games such as The Chronicles of Riddick: Escape from Butcher Bay, The Alien series and Dead space. The player, a prisoner on a space station, about to be overrun by alien abominations. Fighting his way out to freedom. It took me about 2 to 3 months, including learning and prototyping.


Tech Used

  • Dependency Injection (Zenject)
  • Unity Entity Component System - ECS
  • Unity Adressables
  • Goal Oriented Actions - GOAP
  • 2d Lighting engine
  • 2D Platformer Pathfinding
  • Unity 2D Inverse Kinematics
  • Unity 2D Tilemap
  • Unity Cinemachine 2D
  • Unity Post-Processing Stack
  • 2D Motion Blur

2D Platformer Pathfinding

Not going to lie, the custom platforming took more than it should.

Initially, I was using hand-placed nodes and driving actor rigidbodies with physics forces. That caused a lot of problems and was quite unreliable.

Then I experimented with the new Unity ML agents and tried to teach the AI to navigate a complex platformer environment. Suffice it say it didn't work out, as it was taking way more time than expected. It looked promising on simple platforms, but when it came to more complex ones the AI struggled, so I decided to scrap it.

Then I decided to just go about a more guided navigation. I made a post-processing step that calculated and optimized all possible points on the tilemap. It then calculated for potential drop and jump spots. Then at runtime, agents use multithreaded A* to find their way and lerp along the path. When they come to a jump or a fall path node they animate on a parabola between two points.

Unity Adressables

I wanted to make the game as modular as possible, so when I found out about Adressables I immediately wanted to use it.

I always liked asset bundles but never got around to using them. The Adressables solved that. It did take some getting used to, writing and planning for a more asynchronous code, but after a while, it just started to fit in and to work great with ECS, as asynchronicity was one of its allures. I ended up using Adressables for things like items, weapons, and enemies.

ECS

The main reason for starting the project was for me to learn and try the new ECS. I wanted to try and do a more standard game with it, as most of the demos we've seen were quite specific. It ended up working quite well, especially when coupled with dependency injection. The game stayed modular and easy to manage as it grew in scale. I did find it hard to manage so many components and to remember what components were needed for what system. But the ECS debug tools did help with that.

Tilemaps and 2D Ik

At the start, I used Anima2D, as you can see by the robot characters, but once the 2D Inverse Kinematics package was out I immediately switched. It was quite more lightweight and integrated with Unity. I wanted to minimize the art I created, so 2D bones were quite a welcome tool.


Of Course, where would a 2D game be without the tilemap? Especially when I wanted a more dynamic game with some procedural areas. The tilemap was a great choice, as it had a powerful tile API. I've gotten a simple cave generation working for the time being.

I was a bit disappointed that the IK solution wasn't playing that well with ECS. I wanted to rewrite it with ECS in mind but decided not to. Maybe unity will have an ECS solution in the future.

Custom 2D Lighting

From the start, I wanted atmospheric lighting, even in 2D. I wasn't satisfied by the 3D lighting and how it played with 2D, so I decided to search for a specific lighting solution tailored for 2D. I did a bit of digging and saw a GPU accelerated solution built by this guy/gal. I went and made a fork for my own use, made it work with the newer Unity versions and added some features. It is available here.

Cave Generation

Cinemachine

I was pleasantly surprised at the power of Cinemachine and how nicely it integrated with 2D. I even used the Impulse Extension to add some nice camera shake, so that the weapons feel like they pack a punch.


GOAP and AI

I've worked with most of the popular AI systems, like Behaviour Trees, Finite State Machines, Utility AI and even Hardcoded AI. But when I saw F.E.A.R.'s AI technique and what it could bring to the table I was immediately hooked. I've never worked with GOAP before and did have some hard time implementing it into an ECS pattern. But in the end, it worked out fine. I was quite happy with the results, even with the simple AIs I have.

Dependency Injection

Dependency injection was one of the things added to the project, as I've fallen in love with it in recent years. Zenject was my framework of choice. It fit right in with ECS, even though ECS itself had Dependency Injection and I had a bit of hustle bridging the Zenject and ECS APIs.

Post-Processing

I used the unity post processing stack, as it was the most complete package and was the fastest and easiest to expand. I used the standard effects, like color grading, bloom, AA and chromatic aberration. There was one effect, in particular, I really wanted, that I grew accustomed to. I'm talking about Motion Blur. Unfortunately, motion blur requires motion vectors and the default Sprite shaders did not provide such functionality, so I had to extend them and add motion vector capabilities. In the end, it was worth having that sweet subtle motion blur that just makes everything feel smooth and that makes motion feel more impactful.

Final Thoughts

Even though ECS is praised mainly for its performance and ability to play perfectly with multithreading, having a data and logic separation steers you towards thinking differently about the way you approach and design systems. It gives you a lot of power when it comes to memory management and not relying on the Garbage Collection as much. Even if you don't use multithreading everywhere, the ability to so quickly is always there.

I also love the fact that dependency injection plays so well with ECS. Oh, and of course, serialization integration comes basically for free.

In the end, I've become quite keen on using ECS and will probably keep using it in the future. I can't wait for unity to expand on ECS and see where Editor integration will lead us.

Credits and Links

Posted in Game Development on