Published: July 2023, Telltale
Near the end of the development of True Colors, I was moved onto a new project for pre-production. I WISH I could talk more about what that was, but all I can (maybe?) really say is that Telltale had access to an IP that we were going to make a game with, we worked on an on-rails combat prototype that I did made a custom spine class for and worked on the animation handling, something happened that killed funding for that IP, and we were put back on True Colors. The only thing that survived from this project was the code name: Artemis.
As True Colors wound down to release, Telltale approached us again with a new opportunity with Amazon, to pitch a game for them based on their hit sci-fi show The Expanse. We then got back into Artemis and hit the ground running with new ideas and a new big design goal: Zero G and Magboot locomotion.
Unlike True Colors, I was given a lot of free reign over the locomotion system, basically phasing out the entirety of the locomotion system in True Colors- no more giant motion matching assets that make the editor slowly chug along, no more externally built state machines- I was building this from the ground up.
A New Locomotion System
What I would argue is my biggest contribution The Expanse was the locomotion system. The main idea worked like this:
- A Player character was loaded up with Locomotion Mode Actor Components, each handling a specific type of locomotion or gameplay animation state- for instance, there was one component for normal walking, one for Zero G, one for Magboots, one for blow-torching, etc.
- Movement of the actor was handled within the mode, including custom collision handling
- Each mode contained it’s own animation set list, and had defined within it it’s own conditions and transition rules to other modes, like Magboots to Zero G, etc- some modes couldn’t go between each other, like normal walking to anything else.
- The animation BP again had a custom locomotion node, that could queue and play not just two animations, but also had slots for one offs and overlays- anything else special might have mode specific branches inside the anim BP itself. The anim node could now handle blend spaces, as well as a special blendspace class I made called a “Layer cake” blendspace that helped with Zero G veer and speed animations.everything could be controlled contextually with 3D vector control packets
To nicely transition between moving and flying without causing undo motion sickness on take off, it was decided early on to have the player strafe instead of rotate with the movement. To better rotate the actor when using Magboots, we had the actor’s root position be located at the feet. When transitioning into Zero G, we’d actually move the collision capsule and mesh down so that the center of gravity and pivot point of the character was more toward the center.

Before, we moved characters using Unreal’s built in character movement component. However, we had a REALLY big issue with our desired zero G and magboots systems- we couldn’t use Navmesh. Navmesh in Unreal was only meant to work on the ground- it couldn’t go up walls, or on ceilings, so we needed a new way to handle figuring out valid and invalid movement.
For the floor, it was initially pretty trivial – make a potential new position based on our move, make a sphere sweep to the floor, and if there was a floor, move the capsule to the new location and snap down to the floor. This got more complicated however when we wanted to make sure we could still slide along sheer cliffs like they were walls- we tried a lot of things that didn’t pan out, which turned into asking design to just put in invisible walls (since there weren’t too many places that would cause this problem)
For collision – The original plan was to use basic capsule sweeps for the walls, and that worked pretty okay at first, until we had a lot of walls- so I did a little finagling and was able to extract the main collision handling of the Character Movement component and use it for our stripped down movement handling. One shortcoming I had to deal with there was that the built in wall sliding in Unreal only worked on two walls- so I even needed to customize the code to deal with any given number of walls (I think I settled on a loop of 10 to be safe)
When colliding with walls and sliding, we also wanted to avoid the character walking in place, or leaning forward through a wall when crashing into it in zero G. when using root motion, you can create a negative feedback loop when turning down the movement animation since you need the movement animation to know how far you move. so we could never ACTUALLY COMPELTELY stop thge player from moving, or we would never be able to walk away from the wall when we were stopped. but, the value is still small enough that it looks stopped enough!
with that, we’ll get into the more interesting specifics into two modes in particular- the Zero G mode and the Magboot mode.
The Magboot Mode
Because main “grounded” locomotion and magboot locomotion were so similar, it was easy to just make a base mode for the both of them to inherit from. this kept the magboot class itself rather small, as it just needed to handle two things differently- transitions into and out of Zero G, and making the player constantly match the “gravity” of the floor they were walking on.
This went through a couple iterations. I based the design for this dynamic gravity system off of a small throw-away demo I made of a hover scooter not long before. There was a spot on the character (roughly hip height) where we’d send out a bunch of sphere casts, roughly in a sphere shape outwards, and collect the normals of all the walls and floors we hit. We’d create a new normal out of the weighted average of those normals (where parts of the floor more closely aligned and nearer to the casting direction and location were stronger than those tilted away and farther away), and adjust the character’s up to match that.

This introduced a lot of challenges in the beginning. For one, when going past 90 degrees, the character would have trouble continuing walking in the same direction, and would even rotate through the floor. I was able to fix this by ensuring the new floor normal was always calculated locally to the player, so we never got in weird orientations.
additionally, the way we got the floor normals was difficult to keep consistent. If you had the casting location and directions rotate with the player, the normals you got would shift as you rotated between orientations, meaning you might bounce. the same was true if the casting directions were consistent with the world, because it would still move along with the players location. This still worked pretty good, but NOT at corners where there would be oscillations in which normals were the most influential

What I really envisioned was something like a sphere around the character’s feet that was able to return the normals encased within it. I… didn’t know how to do that, so I did something more drastic that could emulate it instead. The basic gist was to do the same casting as before, but instead of at the hip, we do multiple full casts around the sphere around the character’s feet, consistent with the world orientation- This made sure it was consistent at any character rotation and position, so a changing orientation would not effect the result of finding the normal. Some filtering is done to throw out unnecessary casts, but even then, performance tests showed that this was not a big hit at all.

When the player looked in a given direction and pressed the launch button, we’d play a transition animation that would move us into…
The Zero G Mode
Zero G went through many, many iterations:
- Complete movement control with 45 degree stepped rotation controls (scrapped pretty quickly as it introduced unwanted rotations)
- Full 6DOF control (scrapped for not having the same stakes as the zero G in the show, like missing your target and flying off into space)
- Straight shot Zero G
- Straight shot Zero G with small pitch and yaw adjustments
- Point and click Zero G
- always moving forward with full rotation control and brakes
- always moving forward with full rotation, brakes, and back up control
- Full 6DOF control
The circle of life.
The main challenges of Zero G were how to choose a landing orientation, and collisions with rotation.
For landing, I settled on doing a projected direction based on the current forward or up of the actor in relation to the floor- nothing too fancy, but took a while to get a final design on

For the rotation collision, however, I had to get creative. Unreal (at least, at the time) did not have a collision handling solution for rotations without translations. if you moved a capsule you can get collision pretty easily, but if you stuck a collision capsule and rotated it at one spot, it would phase through things as if there was no collision at all.
to fix this, I decided to take a page out of one of my other niche interests – very long, very in-depth videos about the inner workings of Super Mario 64, namely those of pannenkoek2012. In Mario 64, movement is processed in quarter steps- break up your movement into 4 steps, move one quarter of the movement, check for the collision, continue if clear, etc.
First, we break up a rotation theta into a given number k of steps. for each step k, we rotate the capsule by theta/k, and put aside the new rotation. then, since we’re using a capsule, we break the capsule up into a given number of spheres along the height of the capsule itself. we start with the spheres aligned with the current rotation of the capsule, and then translate them in a sphere cast toward the new rotation we made for the step. this creates a facsimile of rotation collision by turning it into a series of small translations! we can then process the collisions at a given sphere cast to stop the rotation early and create an anti-penetration velocity.





Thruster VFX
In the show, the characters are able to effect their trajectory using small thrusters on their suits. In the game, we control our trajectory directly- while we could have gone for full physics based zero G, we wanted to have more responsive movement instead- so to have thruster FX that made sense, we had to figure out the directions of the forces required to get the rotation or translation we currently had.
For translation, this is pretty easy – we take the current velocity and the velocity from the previous update and check the difference. To know if a thruster is supposed to be on, we just need to know if A) the change in velocity is greater than 0, and B) that the change in direction is opposite the facing direction of a thruster.
For rotation, we need to do a little more. We find the delta rotation between the current and previous rotation, and find the axis of that delta. we then take that rotation axis, and find the cross product between that and the direction between the center of gravity and the position of a thruster. the resulting direction will give us the linear direction from that location that would cause the given rotation about the axis. If that direction matches the direction of our thruster, then the thruster is active.

While I think the underlying system is sound, I would’ve liked to go back and re-work some of this, as it’s not the most intuitive to grok just looking at it- sometimes it seems like it matches up well, and sometimes it really seems random.
A Custom IK solution
One thing we wanted walking around was IK for the legs, and even the arms when we wanted it. This was UE4, so the full body IK of a control rig wasn’t quite there yet- UE4 did have, however, a CCDIK node that was still experiential. It worked fine, but had some issues:
- the CCDIK node required all the bones in a given chain to be defined. this is not a requirement in CCDIK in general, so it would be better if I could define a chain as just the shoulder, elbow, and wrist without needing to include EVERY helper bone in-between.
- the CCDIK node had a very limited kind of angle limit, that didn’t include things like hinge restraints or socket restraints
- you had to have a CCDIK node for EVERY chain you needed- each leg, each arm, etc
- CCDIK chains can’t easily be swapped around based on a given locomotion mode
With all these shortcomings- but all the hard work of doing the Actual CCDIK already done in engine- I decided to make a custom version of the CCDIK node that had all the above features needed. that meant simpler chain definitions, the ability to have PER-JOINT rotation limits and different limit types, the ability to define the end joint as “plant” able (to make the hand plant itself palm down, or the feet sole down), and the ability to define multiple chains for one node and swap out and individually control chains based on the locomotion node.
This was used not just for leg IK, but it was also used for the blowtorch gameplay (to make sure the hand stayed on the multi-tool when moving around) and used slightly for when Drummer pushes away debris.

Seamless Transitions
In all our projects, we wanted the ability to seamlessly transition between cinematics and gameplay to help unite the two gameplay states in to a more cohesive whole. The trouble at the heart of things is how cinematics animations are usually captured and saves.
In our cinematic animations, the root bone would be located at the center of the motion capture area. This would help do things like position a lot of captured actors in a scene, since they could all be placed at a shared location. The character would move around this root bone, with the hip bone being the defacto “root”. The problem then, was the handoff between that and the gameplay anim with a simple blend would reveal the issue shown below- the mesh would slide over and rotate to match the ACTUAL actor location and orientation at the root bone that gameplay animations match up with.

In True Colors, we had a baked system for seamless transitions for this- set up a “root transition” event range, and hit the bake button to literally move the actor to the position and rotation we want at the end, based on the current pelvis. the issue is, when small changes were made in timing or position of the shots or characters, that could require the cinematic artist to re-bake the transition.
To fix this, I created a dynamic system. when the root transition starts, we find the target root transform- this is found in The Expanse by casting from the actor’s hip bone along it’s down vector, and returning the first floor we hit. we get the rotation by finding the current forward of the pelvis bone, as well as the average floor direction (in case we go into magboot mode, we want to start at the desired orientation)
Then, for however far we are into the event range, we extrapolate the rotation and position of the root as if we DID lust blend, and find the counter translation and rotation needed in the space of the mesh that we would need to move the root bone to keep the pelvis bone in place. We then modify the root bone, giving us an animation that stays pretty well at the location and rotation we want. We can even do this in the opposite direction, which we use a the start of the shoot out QTE in the second episode to blend into the cinematics.

This was not fool proof- if the pelvis up we start with and the floor up are off, we’ll get some wobble- and if the root bone had a rotation itself, we would get odd blends. While it fixed the necessity to bake the root bone blend, it did not fix the necessity to make sure the root transition was done before trying to find the desired camera position we want to make a key for. And when jumping between sequences in the sequence editor, if the editor was already in the middle of a root transition, that would make give us weird offsets in other sequences if we didn’t clear the effect.
Various other things
Near the tail end of development, I was tasked with creating the puzzle box minigame for the DLC. armed with the design guide, I created a pretty solid little experience that I’m pretty proud of.
I created this with a data/model representation, where the internal state of the puzzle box is not based on the actual locations of the 3D modeled pieces, but instead on a simple 2D integer array.
When we select a piece, I created a dynamic shader where we can turn on and off an emmisive effect on the piece model. If a piece move in the 2D array is valid, we set the new state of the array then send a signal to our model to move the 3D piece.

I worked on the backpack Material as well, creating the system that sets up the lights on the backpack to match with the amount of collectables, or for story purposes, can change the oxygen gauge on the left.


To avoid needing to deal with camera collision, we decided not to do it and set up a dither system instead. I made a new camera rig system that handled this for us, and helped set up the master material that handled the dither. The camera system also allowed for authored camera shakes within UE for effects during Zero launching and landing.

In the DLC, we made a second locomotion mode set for Avasarala, as strafing felt weird for the situation. We tried to add a lot of visual subtleties, like different start directions and rotation leans, but going back to it for this article, I’m not entirely happy with how it turned out- what were supposed to be subtle changes feel really clunky and slow. The part I’m proudest of, however, was the fixes we made to ensure the hair would stay over the shoulder and not clip.

I also worked on the PDC turret aiming for a sequence in Episode 2- the aiming of the turret was an interesting challenge because the pivot of the turret and the gun barrel weren’t at the same location. I talk about what I did there, as well as a solution, documented in this tutorial

