← Back to devlog
Engineering · May 2026

When modularity and Animators don't mix

I love me some good, modular code. Animators seem to thrive on this. Everything is decoupled via parameters and layers. It isn’t enough.

I had a bug. It was fairly late in the project so I had multiple systems in the game. This issue reared its head when I added a cutscene system. The goal was for the player to grab a key, triggering a re-usable cutscene. Turn toward the camera, raise their hands over their heads Zelda-style, and a celebratory dialogue showed. I made the code changes, wired it all up, and it didn’t work. The hands didn’t raise over the player’s head, and they were facing away from the camera. Great.

Three writers, one Animator

Three separate systems were writing to the same Animator parameters simultaneously, none aware of the others:

  • The carry ability was pushing Carrying = false every frame
  • The movement system was re-asserting facing direction from the last known velocity (I’d walked north into the pickup, so it kept locking the character north)
  • The cutscene set the pose once, on entry, and then stopped

And it isn’t the system’s fault. They’re not meant to be aware of each other. The easy bandaid fix was to stop the carry ability from pushing its state every frame, or mess with execution order.

I don’t like bandaid fixes. I like systems that work.

The bug is in the interaction: three writers, one consumer, no priority, chaos. I didn’t want to write a bespoke system specifically to manage this problem with a bunch of my special game logic built in either.

I like to solve problems forever. Once.

The design

What I want is an arbiter. A single, neutral system that all systems who give a care about animations submit to. Then, once the dust settles, interfaces with the animator in a reasonable way.

The value this layer adds, is being able to distinguish data signals by priority.

Two kinds of signals:

Value channels are continuous, per-frame. Any system pushes a value to a named channel (Facing, Carrying, Speed) and the channel resolves to whichever submission has the highest priority. The others don’t fail. They just lose. They can continue submitting. Then, if the higher-priority signal clears, they resume winning automatically.

Commands are one-shot. Fire a command to seek a specific animation state. It holds until it completes or until something of higher priority pushes it off the hill.

In this game, we define priorities as categories to simplify. Things could be more granular if they needed to be:

Priority order: fall > cutscene > ability > movement

Falling is physics. Nothing overrides the player spiralling into the abyss. Cutscenes supersede active abilities. Abilities override locomotion. Locomotion is the default ground state.

With this in place, the item-get cutscene fires a command at cutscene priority. The carry and movement systems keep pushing their values every frame, they’re just outranked. No suspension, no coupling, no logical tangles between things that shouldn’t need to care about each other.

When the cutscene is over, the command is rescinded, and regular gameplay resumes.

Contention stops being a hazard. It becomes the resolution mechanism. And this way, I keep the hairy logic out of serialized data within an animator controller.

Where it stands

Once this system was implemented, the problem solved itself immediately. And other things became clear. More complexity in the animator seemed unnecessary. More logic to be moved to a proper place. I’ll integrate this into more cases. Props, NPC’s, Enemies, until all the kinks are worked out. So far, I’m pretty sure this system will outlive this project.

Just like I wanted.