Among the various things I worked on this week, I spent a good portion of time implementing a basic fluid system in the game engine. The fluid system lets me create volumes of moving fluid that alter the player’s physics and can flow down off ledges and interact with objects, so that levels like the aqueducts look and feel more convincing.

The two major pieces of the fluid system are physics and rendering. The physics system is arguably more important, but ended up mostly being a matter of fine-tuning. Here’s how it works:
Previously, every frame I did a sweep of the area below onscreen entities to locate the nearest surface to stand on, and used that to perform some motion and collision detection calculations so that the player can properly move up/down slopes, stand on moving objects, and drop when standing over a gap.

To implement the water system, I extended the standing sweep code to also locate the nearest volume of fluid that intersects the entity, and store it. What this allows me to do is detect whether or not the player character is currently inside a fluid, regardless of whether or not he’s standing on a surface at the time (so it even works while jumping/falling).

Once I had this functioning, I added the ability to attach ‘Material’ information to any piece of geometry in the game, including fluids. These two pieces of functionality combined allow me to control the physical attributes of a surface, and then further alter those attributes when the player is standing inside water or other fluids. This not only allows me to implement slippery surfaces like ice, but cause the player to move more slowly and have different control characteristics when in the water.
In the future, this should also allow me to alter the characteristics of surfaces in response to gameplay events – enemies that spray sticky goo on the ground, spider webs that slow you down, or a potion that freezes water, turning it into slippery ice you can stand on.

After a bit of fine-tuning, I came up with some physics parameters for the basic material types that I’m happy with, that make the differences between various materials obvious without breaking gameplay mechanics. The jumping mechanics were a particularly tough detail – even minor changes to the acceleration and speed characteristics of the player render some of my jumping puzzle prototypes completely impossible to solve, so I had to carefully test against those puzzle prototypes each time I adjusted the physics values for a given material. In the final game, I’m going to have to address this by only using a specific set of materials in each level, so that it’s not necessary to retest the entire game after physics changes.

Of course, water isn’t particularly interesting if you can’t see it, so the other thing I did was build a fairly simple bit of rendering that allows fluids to flow across surfaces and down off ledges.

The basic approach is that I designate a rectangle as a fluid volume, and then can mark it as ‘flowing down’ on the left and/or right sides. This gives me a simple way to produce relatively realistic water configurations without having to spend a bunch of time fine-tuning.

The rendering logic is for fluid that doesn’t flow down is relatively straightforward: I walk horizontally across the rectangle and generate some textured geometry to represent the surface of the water. To make it animate convincingly, I animate the water texture across the surface and perturb the Y coordinates of the geometry based on the current time, so that it appears to sway and bob as if from waves/ripples. Not as good as a real water simulation, but it looks nice. At this point, we have standing water that looks pretty good, but doesn’t have any obvious reason for being in the game world – where did it come from, and why is it moving?

So, to make the water look more like a part of an interconnected system instead of just a static prop, we make it possible for it to flow downward. A fluid volume can flow down on either of its sides. Setting it to flow downward causes the game engine to project a ray down from the edge of the fluid, looking for an obstruction. Once it finds an obstruction below the volume, it generates geometry spanning the distance between the edge of the fluid and the obstruction.

water_01

With some slightly tricky math, we can smoothly apply the animated water texture to the geometry so it looks as if it’s flowing down over the edge and hitting the obstruction. The key here is to carefully interpolate the texture coordinates so that there aren’t any seams where the horizontal surface meets the vertical surface – the easiest way to do this is to simply walk around the curvature of the corner section, generating a pair of texture coordinates for the inside and outside of the corner that smoothly transition between the texture coordinate of the vertical surface and the texture coordinate of the horizontal surface.

Unfortunately, you can’t just spit out a few vertices here and let the video card take over, since it interpolates values on a triangle/fragment basis, and generates results that look particularly strange. Finally, we can make things look slightly more convincing by applying stenciling so that different fluids won’t draw over each other. Once that’s done, it looks like this:

water_02

Now the water itself looks pretty good – there are some minor artifacts, like the visible seams created by the combination of alpha testing and stencil masking, but otherwise it looks quite nice. But it still feels as if it’s not a part of the environment – it doesn’t have any interaction with the level geometry or the objects in the world, other than the fact that it can’t flow through obstructions. So to make it feel more like a part of the world, we can add some particle effects – some at the point where the fluid stops flowing down and hits an obstruction, some at points where the fluid is making contact with an object (like when the player lands inside a fluid), and so on. Doing that gives us this slightly improved result:

water_03

And finally, you can see a slightly outdated version of this system in motion: