Using clj-statecharts to Manage Character Animations
I’ve been spending my spare time doing some game development in ClojureScript, and recently I needed to solve the problem of dealing with animations for the player character. Having used the Unity game engine in the past, I knew that finite state machines (FSM) were a great way to manage transitioning between different animations for a character, based on underlying state changes.
For my game, I am currently dealing with 4 animations:
idle
- When standing stillrun
- When movingjump
- When jumpingattack
- When attacking
These animations come from the Kenney Character Assets bundle
So, I spent some time building out a very poor FSM implementation for managing the animation states and transitioning the animations when the character’s state changes due to player input.
I soon learned that my FSM implementation was not going to cut it as my animation needs were a bit more advanced than I originally thought. I found two issues:
- The
idle
,run
, andjump
animations were mutually exclusive, but theattack
animation actually needs to be blended with the other animations. - For the
attack
animation, I also wanted to emit an event at a particular time in the animation to signal that the “hit” procedure should execute, as the sword-swing doesn’t really land until about 1/2 second into the animation.
I was going to start researching the different FSM libraries available for Clojure, when, literally the next day, I saw a post on the Clojure subreddit about a great library called clj-statecharts by Lucy Wang.
After reading through the documentation, I decided to try using it to replace my terrible FSM implementation.
Here’s a simplified version of what my character animation FSM looks like in clj-statecharts:
A few things to note about this FSM:
- The only event ever sent is the
:tick
event, which is fired on every frame. I make extensive use of guarded transitions to determine if a transition should be made based on the underlying game-state. This is represented by all of the predicates (airborne?
,moving?
,attacking?
etc) - The undisclosed
play-animation!
function deals with playing the animation via theAnimationMixer
in Three.js. - The undisclosed
emit-event!
function simply emits an event to character’s event-system - I’m using parallel states of
:upper-body
and:lower-body
to separate the animation layers. So the character can be in both a:run
state for the:lower-body
and:attack-start
state for the:upper-body
- I’m using delayed transitions to handle emitting the event after 450ms have passed since the attack animation started.
- I’m using a nested state machine for the
:grounded
state to toggle between the:grounded
and:airborne
states
Overall, I’m really happy with how well clj-statecharts worked for this. It feels much easier to manage than my previous hacky FSM system, and it will be a very useful tool to have for other aspects of my game.
Here’s what the result looks like in-game: