If a player summons two elements that merge into another element, but the player didn't see the merging happen... did the merging even happen?
Not really! From the player's perspective the game is just unpredictable.
But, our new animated spellbar fixes that - and it was damn tricky to build!
Status Quo
A few weeks ago I implemented a quick and dirty spellbar:
Since elements combine instantly, spells can be cast straight away - which is really nice.
The only problem was you couldn't tell what element combinations actually happened - even I had trouble telling bugs apart from intended behaviour.
I wanted something like Magicka's spellbar, where you can see the elements combining visually. 1
Clarity vs Responsiveness
As I started implementing an animated spellbar, I realised I have two conflicting goals:
- Responsiveness: players should always be able to cast a spell as quickly as they can press buttons.
- Clarity: players should always be able to understand which elements combined (or cancelled each other out).
So the existing interface had great responsiveness, at the cost of terrible clarity.
But, animating the element combining to improve clarity would also make spellcasting less responsive: a poor implementation would make players wait for Fire and Water to merge before they could cast their Steam spell.
Representations
My first idea was to enable "fast forwarding" the animation: if the player triggers a cast while the animation is still playing, then just run the animation 1000 times faster - the animation will finish near-instantly and the game can use the result to cast the right spell.
That could have worked, but seemed hard to tweak and debug. And someday I'd like summoned elements wizzing around the player character and visually combining & cancelling there too; I would have to somehow keep that in sync with the animated spellbar.
So instead I made two separate spellbars:
- An Instant Spellbar, where elements combine and cancel instantly. This simple, but hidden from players and only used to figure out what spell should be cast.
- An Animated Spellbar, where all the element operations are animated. (This is super complicated, but we'll get to that.)
Internally each element combination is expressed as a rule (e.g. there must be only 1 Shield), and so each rule gets checked against the Instant Spellbar and necessary fixes get applied (like insta-removing the excess Shield) .
But! Each fix for a violated rule is recorded so that the Animated Spellbar can schedule an equivalent fix-up (like slowly fading out the excess Shield).2
That was a ton of words, so here's the new spellbar in action:
Animating Elements
Animating elements (in response to those recorded "fixes") to get the above is tricky:
- An affected element might already be getting animated:
- E.g. an element needs to combine with an already-moving element!
- But if two combinations are happening at once, they should happen in parallel.
- When new elements are created, they need to appear in a sane spot even considering other animating elements.
- And there's only limited space to display the elements.
- Removing an element means all elements to the right need to move left one slot...
- But those other elements might be busy animating, and maybe when they're done then they won't need to move anymore?
- You don't want to move too many elements at once, because it's confusing...
- But you also don't want the animations to be so slow that they're backing up!
So if you don't have the right design, you get something like this:
After the false starts and a bit of brainstorming,3 I ended up with an animation graph:
- Each animation controls only 1 thing, like rescaling or moving a single element.
- Each animation can be blocked by other animations.
- When an animation X finishes, we kick off any animation Y that X was blocking (only if Y has no other blockers).
So each "recorded fix" results in a few of these animations being queued up; I also make each animation blocked on all previous animations that affect the same element, which minimizes the amount of concurrent changes.
This means that a Fire combining with a Water is represented like this: 4
And for fun, a Water absorbing 4x Lightnings is represented like this:
I also fiddled a lot to get the speed to feel decent:
- If the player has summoned a lot of elements lately, animations play faster - that helps reduce "backlogs".
- Element-combining animations use an easing5 that lets the moving element slow down as it approaches, to let the player see what's happening more.
Here's a more complicated example to demonstrate:
The graphics are still placeholders, of course.
Stress testing
Naturally this was a major pain to flush bugs out of, so I added a little stress test:
Playable web build
Alright here you go: summon some elements with QER/1234. (Yeah, I'm trying out not needing to hold Shift - seems good so far?)
(Pssst. If you look around real careful-like you might even find the new spell I implemented too.)
Here is a nice example video.
And a future "elements all wizzing around the player character" display could also update itself based on those recorded fixes.
Thanks Zeddy!
I'm using petgraph as my graph data structure, which can output graphs in DOT format; then I have a debug option that invokes GraphViz to render the graphs as PNGs like those shown below. It's been super useful for debugging!
Mathematical functions which remap 0-1 to some other numbers - check out easings.net.
In this context, they're an easy way to make animations "start fast, finish slow", "bounce when reaching destination", etc - which can help players understand what's going on.