Well, a lot of time has passed since the last time I made an update to the ECS Survivors project. Life has a way of well... getting in the way of things, or you could also say I got a bit lazy. But at long last, I've come to tell you all about the shiny new features that ECS Survivors holds!
In this instalment of the series, I'll cover the many systems and features that were introduced over the 4 "parts" or 7 months (has it really been that long!?!). My memory might not recall some of the details for each update, so I will give a surface-level explanation rather than going more in-depth as I did with the other posts. Without further ado, I now unveil the weathered, dust-riddled curtains! (I've been reading a lot of Tolkien recently and find myself in awe before his imagery, metaphors and similes. I want to integrate that style into my casual writing, so sorry about that 😄)
Part VII - Tilemaps
I wanted to give a little life to the game; So far at this point, the background was of a simple, dull gray. Tilemaps are fundamental to most 2D platformers, top-down adventures, survivors, and others of the likes. In this instance, I didn't want to reinvent the wheel and create my own over-engineered level-editing tool, no! Tiled is a fantastic tool to quickly create intricate levels and embed metadata along with them. Identifying collidable objects, such as the map edges very easy. I used the tmxlite library to load-in the tilemap file. Early on, I simply created a new tile instance for each of the ones specified in the original file and drew each one individually. The same logic was used for the colliders of the tilemap; I created a box collider for each tiled which has the embedded metadata.
Now, the keen of you will notice that both of these implementations introduce sub-optimal behaviour.
For one, drawing each tile individually, while not a tremendous issue, can quickly get out of hand as the size of the map increases. A simple tilemap of size 64 by 64 will result in 4096 draw calls, and that's just too much to do every frame. A rather clever and trivial solution exists. We will only draw each tile once, then take a "screenshot" using the Raylib RenderTexture, and from that point, we only need to draw the screenshot, reducing the draw calls to 1. Later, we might have dynamic elements on the tilemap, such as destroyable boxes, opening doors, and those we can draw individually, since there will be very few.
The second issue is quite more performance hungry. Again, in the case of a 64 x 64 tilemap, in the worst possible case, there are 4096 new colliders. This is unacceptable for performance so we need to simplify the colliders. A basic, yet efficient approach is to implement some sort of greedy meshing algorithm to maximise the surface area of the colliders. I found this blog post for Roblox, it breaks down the algorithm in depth. Using this technique, we can drastically reduce the amount of colliders, especially in larger areas such as the map boundaries.
Before: Each tile has a collider (30 colliders) | After: Colliders are merged (4 colliders)
Part VIII - Accelerated Collisions
At this point, our collisions worked well, as soon as we want to scale up the difficulty of the game, by introducing thousands of enemies, then our current, quadratic, implementation of collision detection and resolution will not be up to the task. Hence, we will add a simple yet efficient acceleration method for collision lookups — a spatial hashing grid. Simply put, right now, when we detect collisions for one entity, we perform checks with every other entity (n x n). In a spatial hashing grid, we split up the area into multiple cells, and now looking for a collision with an entity will only look for other entities within its neighbouring cells. I recommend you look at Ericson's Real-Time Collision Dectection Book (2004), specifically chapter 7.1 - Uniform Grids. There is not much else to talk about for this update, apart from maybe the performance gains from the acceleration structure. We go from 600 to 7000 colliders now, more than 10x performance gain!
... continue reading