Arbitrary Gravity Character Controller
Adding arbitrary gravity support to Unreal's character would require major overhauls to its movement component. I therefore opted to design and create a bespoke character controller from the ground up. As a starting point, I ported from Unity and redesigned an old physics-based character controller of mine. Working off a concept I was already comfortable with allowed me to rapidly lay down the foundation for Little Cosmos for the rest of the team; this was crucial in a fast-paced and otherwise unfamiliar working environment. The physics-based character controller approach came with some additional benefits specific to our platformer, such as supporting force-based gravity.
Being my first time working with physics at the core of the player experience in Unreal, I entered the project largely negligent of the implications that may come from applying physics outside a fixed timestep. The issue was largely unseen until the final stretch of development. As the map got filled to the brim, we found framerate began to drop; with this drop came an unpredictable and grossly inaccurate physics simulation. Being made aware of the problem with only a couple of days of development remaining meant that quick thinking would need to come to the rescue. With insufficient time to implement an optimal solution, such as a substep tick function, I opted to use a fixed frame rate of 60 fps. With some QA and profiling, we were able to identify framerate drops to be most drastic when 'looking up' at large sections of the level. The artists were then able to quickly identify which particle effects and shaders were least performant and remedy the problem. In the final build, we all achieved a consistent 60 fps, free from hiccups.
Since wrapping up Little Cosmos, I've been independently developing the systems used. The first priority in doing so was to experiment with using a sub-stepped tick for applying physics. See the results below for yourself! For those interested in learning more about this topic, here are some excellent resources I came across whilst creating my own sub-stepping tick in Unreal: Giuseppe Portelli (Theoretical), Måns Isaksson (Practical).
'Any Shape You Like' Gravity
From researching Mario Galaxies approach to gravity, I learnt that Nintendo used discrete regions of gravity fields placed around the level. Whilst this worked for a large company like Nintendo on a game with many years of development, I was a little sceptical about limiting our gravity fields to certain regions. What if the player escapes all regions of gravity, or multiple regions are overlapping? As our team lacked a dedicated level designer, I instead opted for an approach that was more programmatic, less demanding on designers and frankly, more interesting for me.
Calculating the direction of gravity for a position in the vicinity of a spherical planet is just a matter of calculating the vector from the sample position to the centre of the planet. But consider a non-spherical planet, such as a cube. Our gamified expectation is that the direction of gravity will be at a normal to the planet's surface. However, what happens when we are not on the surface, but somewhere 'above'? Even if we are able to determine the nearest point on the surface, we will experience dramatic changes in certain situations — particularly for shapes with sharp corners.
We're close to a solution, but not quite there. Let's backtrack and consider what other measurements might be useful to us. Gravitational force is a vector comprised of both direction and magnitude. In reality, gravity's magnitude is determined as a function of the masses of the bodies involved and the distance between those masses. That distance is often simplified to be the distance between the centre of the planet and the other body. Whilst physics is a wonderful starting point, we must break physics in order to make something extraordinary. I propose that a more useful measure of distance for a platformer with a variety of planet shapes and radii is the distance to the surface of the planet. For instance, take the case of hopping from a planet with a large radius to one with a small radius. Intuitively, the large planet should be the main effector until your jump has reached a height of roughly half the distance between the planet's surfaces, at which point the main effector should become the smaller planet. If we wanted to adjust this switching point, we could simply assign a 'mass' value to the planets by which their influences could be scaled.
A common computational technique for finding the distance to the surface of a shape is signed distance functions, typically limited to shaders. By representing planet's shapes using signed distance functions, we are able to determine the nearest point on the surface of any planet. Furthermore, computationally differentiating the signed distance function gives us an approximation of the direction to the nearest planet's surface. Approximations can be scary, but I found the error of the approximation to be completely insignificant with even first order, runtime suitable methods of calculation. And that's it! Just find the signed distance function of a shape and form a planet of any shape at will. Thankfully, Inigo Quilez has compiled an assortment of signed distance functions for our choosing.
Baked Gravity Fields
Whilst gravity was calculated at runtime in Little Cosmos, I also experimented with baking the calculation into vector fields; you can find a prototype for this application here. The approach may be beneficial beyond just performance gains! The exported vector field file type is the same used by Unreal for particle systems and the chaos physics system; there's certainly more to be explored!