During development, once we had gotten to the point where we had implemented the one bear trap alongside the full gameplay loop, we started to have an issue. The game felt shallow, and there was no reason to explore or play around the map.
We decided to focus our efforts on expanding the arsenal of traps, specifically adding an electric and a puddle trap. The intent with this would be that they would work together, and then we would have sources of electricity and puddles already in the level for the player to work with.
For the puddles, there were some criteria that I wanted them to meet:
Accurate colliders
Interaction with walls
dynamic generation at runtime
In order simultaneously fulfill dynamic generation and proper interaction with walls, I had the idea to retool the pathfinding code to spread out the puddle to the nearest N nodes, with N being the size of the puddle. This would allow the puddles to not only stop at walls, but also spread further in narrow hallways and always maintain the given surface area.
I was able to reuse the search space defined for the pathfinding, which worked great because it has information on where all the walls are, as well as the necessary data for running Dijkstra's algorithm.
Once Dijkstra's algorithm runs, it takes every node on the open list, as well as every node next to a wall. Together, these represent the outer bounds of the puddle.
In order to turn it into an actual puddle shape, I made a convex hull algorithm, which takes all these nodes and sorts them in order of their angle from the center, then prunes every concave angle.
This leaves us with a rough polygon that bounds the puddle, which I then integrate straight into the physics engine as it's collider. This was an added benefit of implementing polygonal colliders using the Separating Axis Theorem, since they work well for detecting overlap.
For the visuals of the puddle, I run the same re-insertion algorithm and Catmull-Rom spline that the pathfinding uses to turn the rough polygonal outline into a smooth, fluid shape.
Once the above code has run, we are left with a puddle that matches the shape of the map and has an accurate collider, but we want to give the player the impression of it actually spilling out.
I could have just scaled the puddle from 0 to 1, but this would have given it a very cartoony look.
Instead, I interpolated each point using a circular ease out with respect to the furthest point on the puddle from the center. This made the puddle grow the same speed in each direction, and stop when it hits walls at the proper time.
There are two features I would have really liked to implement with the puddles to make them more realistic, but they would have been too difficult to implement to justify being only a secondary mechanic in this game.
First would be concave shapes; The puddles only exist within spaces that the spreading algorithm determines they should, but because they need to be turned into convex polygons, they sometimes have the appearance of going under corners. Concave shapes would have required a completely different algorithm for the following:
Collision
Determining the mesh
finding the outline of the puddle
The second feature would be dynamically combining puddles, which would rely on concave shapes to be even possible. This would also entail implementing an algorithm to prune overlapping nodes, adding the intersection points, and redistributing growth amounts around the new puddle, since intersection would always happen when one or more puddles are actively spilling.
Implementing these features would have been totally over scoped for this project, especially considering how the puddles are only a secondary mechanic. Play testers never once complained about puddle overlapping as well, so it wouldn't have even benefitted gameplay. For another project, implementing complex puddles like this would be very interesting, perhaps for a puzzle game, but the puddles we ended up with work perfect for DREAD IT.