28 Feb 2013
February 2013 - Δg (delta g)
2d3dunityplatformersidescrollpuzzlephysicstimegravitypostmortemdigital

After some false starts this month and some prototypes that I realized I couldn't finish in time, if at all, I wasn't sure I'd be finishing a game this month. Then one night I decided to try to implement variable gravity, particularly gravity created by a local object (Mario Galaxy is the most well-known example I can think of). Things kind of snowballed from there.

The Plan

I finally landed on a physics-based side-scrolling platformer where the player had these capabilities:

Basic Movement
Walk, run, modest jump, slightly limited air control.
Local Gravity
Touching specific surfaces sets the player's "down" to the direction of that surface. Specifically, the inverse of the surface's normal at the point of collision.
Gravity Inversion
The player can instantly invert local gravity to its exact opposite.
Force Zones
The player can create zones anywhere in the play area that exert a constant force in a configurable direction and strength on moving objects within the zone, including the player.
Time Distortion Zones
Like force zones, except they distort the local time scale of any object overlapping the zone. The time scale multiplier is also configurable by the player.

I realized I was looking at the makings of a puzzle-platformer here, so I decided it would be in the form of an obstacle course, rather than involving hostile entities and combat. The obstacles were typical platformer fare, with the addition of force zones and zones that instantly set the player's local gravity to a specific direction on contact.

That was working, so it was time to generate some puzzles. The biggest revelation at this stage was that if it's possible to create puzzles with the above abilities and obstacles that are both challenging and enjoyable, I'm not prepared to do it. A few changes had to be made in the course of puzzle design and testing:

Arbitrary Gravity - Adjusted
This is the part where I learned that the normals on collisions in PhysX by way of Unity are a complicated topic. If you're under the impression that an axis-aligned box collider travelling along (0,-1,0) hitting another box collider's surface at y=n will produce a collision normal of (0,1,0), you're right. Sometimes. Other times, you are not. The explanations for this seem to be subtle and mysterious, and this causes significant issues when you use that value to determine gravity. I modified this code to use a precision setting that will align the calculated gravity to specific steps, e.g. orthogonal platforms will only return a normal of (0,1,0), (0,-1,0), (-1,0,0) or (1,0,0), while platforms that are angled can produce 0.5 versions of normals, etc., and that seemed to solve my issues.
Gravity Flip - Gone
The ability to both reverse gravity arbitrarily and create zones that can change your course mid-flight made it far too easy to trivialize a lot of puzzle concepts, and really made the whole force zone mechanic superfluous in a lot of situations without being nearly as much fun.
Gravity Zones - Gone
These were too buggy and unpredictable to be used. However, I could use a lot of the same ideas by just creating force zones that force the player in the direction of a surface that would set their gravity as needed.
Zones - Limited to One of Each
Much like gravity inversion, having multiple force and time distortion zones trivializes a lot of platformer content. If I were to flesh this out to a larger game at some point I would probably unlock the ability to spawn two or three of each type of zone, but that kind of complexity wasn't going to happen in this time frame.

In the end, what I had was a short tutorial and ten puzzles in an obstable course layout, with music by my buddy Ben Freund and a nice CC-BY robot mesh from HJ Media Studios. It could probably use a lot of tuning and player testing, and there are plenty of thorny little edge cases and collision overlaps that could use some finesse if and when time permits (walking around the corner of a 4-sided gravity surface is particularly spastic), so I guess I'll see what I end up doing with this code later on. But for now, here it is, in a generally playable and hopefully entertaining state.

Note: the following builds are available for testing purposes but I don't have the ability to test them. These are Unity builds so I don't anticipate any serious issues, but I can't vouch for correctness or performance of these builds.