Avoiding path-finding AI while optimising.
The answer is not much, I know how AStar and Dijkstra's algorithms, but to implement them for the game I would also need to be able to generate a dynamic node map, so that when designers build a level they can put the enemies in and get it working immediately, or more strenuous they would likely need to be able to edit the map. It's not an impossible task, but a time sink, and working in custom engine with a limited amount of time, I thought it would be important to pick my battles, I'm less excited to show off my AI knowledge, and more excited to show off my ingenuity!
Understanding what I want from the enemies in the game is the first step, a quick chat with the design team and a small read of the GDD. The enemies needed to flock, encircle the players, and navigate the environment.
Boids come in handy for some of these boxes, a boid is typically made up of 3 forces, a separating force, a cohesion force, and an alignment force. These three keep boids from hitting each other, moving the same way, and collecting, however the balancing of these forces is rather agile, and adds a lot of room to add additional forces. To tick the remaining requirements we could add a force towards the nearest visible player, and away from possible collisions. Now there are 2 new problems to solve.
Thankfully to get Sync's railgun working, early in development I got a raycast written up, however it may not have been the most optimised, but we can work around it.
We make sure that all enemies raycast to see if they can see a player, but instead of pilling this up every frame, they cache a "frameMod" a number between 0-11, that they compare with the frame count moduloed with 12, this frameMod is staggered with each enemy spawned, so that instead of raycasting each enemy each frame, enemies can do the raycast once every 12 frames staggered amoungst each other. A simple fix to give us the to player force.
It's important to note that the higher the number of frames you wait to recast, the more inaccurate the boids become, and the more often they cast, the more performant the enemies become.
The biggest problem then became colliding with the environment, what I wanted was a simple at runtime operation, which could be offloaded by some work during level editing, the environment of the level never changes at runtime so lets use that to our advantage. That means taking the process to an editing step, such as saving the level, so it became pretty clear from here, since the games physics is entirely in 2D this can be used to our advantage, by creating a signed distance field of all the colliders on the level, its easy to make a texture from there of everywhere that a collider isn't, and the distance it is from the nearest collider, this value can be a linear scalar of the force that the boid should experience, and then the displacement from that point to the nearest point on the collider should give us the direction a boid should move. Using the displacement from the point rather than the normal of the edge should help mean that corners of colliders have a softened edge.
Now look at that, we have a flow map of the level which helps boids move away from colliders, reading this into an array at the start of every scene should help set us up to succeed, but there becomes one major issue, working in this way isn't designer friendly, automating the process means places on the map could end up having enemies get stuck.
So in addition to creating the flow map, a guiding tool was also made, just a simple unit circle of directions that designers can use to paint over the top of the saved texture and use it to encourage enemies.
Enemies now move towards the player, and it didn't take too uch out of our short time to get it in motion. As we have been testing our game, enemies still cause a bit of lag, If I had more time I'd go back and do a similar texture look up for the line of sight, where I'd create an SDF and look up along its line, but that's for future me to worry about, less future me is enjoying the sunshine!