Using Montages as all-in-one attack definitions
Added 2023-07-28 00:49:30 +0000 UTCThis isn't so much a dense piece of code as, a way of thinking of existing systems to make them do more than you might expect.
In this spirit, let us consider the humble AnimNotify. Easily missable, and so not to be used for required-reliable actions, since for anything that necessarily has a start and an end, you might blend out before the end notification. But then let us consider their sibling, AnimNotifyState. Ah! Now here is a system with class. It can do everything an AnimNotify can, with the bonus of unmissable end-events. If you blend out mid-NotifyState, it just calls the end early. Fantastic. Now what can we do with these?
Well, how about everything?

There's a bunch of versions of this. You can use curves for the same result, because querying the curve value for a curve that doesn't exist has a safe default (it just returns 0). But, I don't like curves, they're fiddly to architect. I like states! Though it does take a tiny bit of fiddling to get what you need here.
The problem is mostly Blueprint. See these arguments?

You can't touch these normally from Blueprint, if you were to address the AnimNotifyState directly. That's because of this thing:

This one fucking class isn't BP exposed, for god knows what reason. But that's ok, if you know a tiny bit of C++, you can simply extract what you need by making a very, very simple Blueprint Function Library function that does this:

Then you've got a node you can drag that EventReference into, and you get the actual Notify out. Now we're cooking with gas! Oh, don't want to write C++? Ok, there's actually another way:

So long as your AnimNotifyStates are smart enough to, on start, feed their arguments pro-actively into the thing playing them? You never have to swim upstream to get that data. In this case, this is a MovementOverride, which is a thing in my engine. Suffices to say, I use this to say "for that frame range during this attack, slide the guy forward, because the animation involves them stepping or sliding forward". Could I do this with root motion? Yes. Am I? No. This isn't a post about root motion, so we're moving on, heh.
Now this really is it. There's no greater magic. Set up your AnimNotifyStates like this, using their Begin/End to feed or pull arguments into/from the thing playing that montage, and there you go. You can do literally anything with a montage.
See this? This is how I handle shield blocking:

Ignore the actual animations, I've spliced this together from a couple of placeholder sequences, but note the sections. This includes the drawing of the shield, the looping bit where they hold the shield up, and the sheathe when they put the shield away. Could I implement this as a state machine in my anim blueprint too? Yes! In fact you SHOULD do that if you want a shielding state that smoothly transitions across a bunch of possible states. But. In my case, shields and the concept of shield blocking are just a quirk of if you happen to be using this one particular off-hand weapon type, so, I do it this way. It puts the shielding on even footing with, you know, an off-hand dagger that can't block but can attack or, whatever else, pistols or what have you. Handy!
So yeah! Your animations can do waaaay more than just animate. They can drive the whole dang logic of a movement if you want them to. And yes, before the person in the back starts yelling- this does, in fact, work in multiplayer too. Not saying it's the best approach if you're making that kind of game, but. It works. I have it working fine. <drops mic>
Comments
Note from future me, I've since walked back a bit on using the arguments of the NotifyStates themselves to store combat data. It works fine, but it means your combat data is fused to your animation data, which sorta violates data-driven design principles. You might not care! The difference is just that instead of storing the data in the actual arguments, I instead trigger these montages within the context of a PrimaryAssetData class which stores the montage and the relevant data like damage-amount or whatever. Then when we want those arguments, we reach into the currently-running-attack's data asset, instead of the montage arguments itself. That's all!
Megan Fox
2023-09-21 19:29:34 +0000 UTC