Animation event sounds like potential danger

Isn’t animation event really bad since it can just call any function (public or private, doesn’t matter) from any child script component under Player game object without them knowing? Or is there some way to prevent that or did I understand something wrong? Especially function like Hit() is very popular name and I wouldn’t be surprised if that would cause some unwanted behavior for someone.

That sounds very bad but if that’s how Unity works then so be it. I guess at least naming animation events with some prefix would be good way to prevent some problems.

5 Likes

yah, naming the event something extremely specific would save your bacon… hit is WAY too ambiguous for my taste

Unfortunately, this is already baked into Explosive’s animations. To be honest, I haven’t run into any conflicts from Unity MonoBehaviors, so the challenge is to make sure that only the scripts you want to react to hit react.
Animation Events are part of Unity’s core structure, and while they do break the OOP convention, they’re fundamental to modern game design when timing is critical.

1 Like

Just to reintegrate this topic since it’s still something quite present today, using animation events is just 1 of many possible ways of handling the hit() or whatever function being called.

It’s true that it’s not really a safe way, but it’s good for the purpose of testing things out. It also gives you the proper timing values if you were to use some other methods.

If you’re looking for safety, then it would be safer to use a private Enumerator that get started at the same time as the attack trigger gets called and have a specific wait time based on the attack animation. If the attack is canceled due to any unforeseen event, you can just force-stop the Enumerator through various ways like setting up a While() that make sure the character can attack or just using a StopCoroutine() while caching the used Enumerator.

The pros of using Animation Events are:

  • It allows quick testing and preview of the timing.

  • It allows to setup an function call with precise parameters values based on the animation.

  • As it’s tied with the animation, it’s also tied with Time.Deltatime internally for starting, meaning that there’s no inconsistencies between the animation and the event in any normal situation.

  • It’s a call event that gets added onto a frame-based list of call within the engine. This means that the event is never skipped.

The cons of using Animation Events are:

  • It’s not a secure approach because the event is baked onto an animation in a rather simple way and it’s relaying on a public call. (It’s quite easy to manipulate or simulate with any Unity-compatible cheat engines.)

  • Animation Event are radio call (floating calls). This means that whenever an Animation Event is being called, it’s a 2-frame setup where, on the frame it’s called, it’s sending a public call to any relevant components telling them “Call X with ABCD parameters if you got it” and, on the next frame, the component who has X calls that function with the given parameters (if applicable). A major issue with this setup is the relevance to the timing and what’s recovered if the timing is off. While the animation state are tied to the DeltaTime so they will skip part of the animated state if there’s a low FPS spike once it recovered, the Animation Event are only delayed and won’t get adjusted to the new timing. This is because the engine don’t know that the previously “X” mentioned actually is until the component (script) calls it by the next frame so it doesn’t know that what’s to recover or not on that end. If you display a strike effect on a specific frame of an animation by just using an Animation Event and there’s a low FPS spike, the strike effect will only be played once it recover which, in extreme cases, could be after the strike animation even ended (if it’s short).

  • Since it’s tied to an animation state, it’s also incoherent from any change of states as long as the animation is being played internally. The best example would be if you had a fast attack animation and a blended transition to Locomotion/Idle that last more than the time it takes for the animation to call the Hit() Function. Unless you prepare some countermeasure through the code to ignore the Hit() when undesired, it would call the Hit() function even as the player stop attacking and switch to Locomotion/Idle. This can get extremely more complex to manage if you consider having multiple layers of animations at once as every layers’ Animation Events (on active animation state) are then called together.

My personal approach to this is use a private coroutine approach for anything I want secured and fixed like any timing and hit detection and use Animation Events for decorative stuff like visual effects. It requires a bit of work from making a fixed database of base timings and stuff and caching the coroutine(s) to allow me to stop or run them properly, but it also allows me to do a double-check from the Animation Events to make sure the visual effect is still relevant or to just ignore it if it’s too late.

All valid issues.
For that last one, we generally stop the attack animation (in the RPG course) for one of three reasons…

  • The target dropped dead.
  • The target bravely ran away (cue Monty Python minstrel here)
  • The current action changed (I know I was fighting that guy, but look, there’s a flower I need for my herbalism!)

In the 1st and 3rd cases, the target is cleared, meaning that a simple

if(target==null) return;

suffices… Hit() fires, but the Hit() method says “Yes, but I feel like you really didn’t mean it!”.

The 2nd case is a bit trickier, and it has come up with students before. For this, a simple change to the line above works

if(target==null || !GetIsInRange(target.transform)) return;

With using coroutines instead of Animation events, you do tend to get into complications like knowing the correct time for each possible animation. For example, in my setup, there are actually 10 Attack states, chosen at random as each attack commences. When swapping weapons, each of these attack states are swapped out with the new controller. It doesn’t take long before you start to overwhelm a database with timing values (that you have to locate yourself as you build the db).

By no means am I saying don’t do this, it sounds like you have that working already, and good job! I just lack the patience to populate a db like this myself.

Yeah. Due to the derivative nature of my project (if you look at my other post on previous course related to how I’m making a Metroidvania style game instead of a pure Diablo style game), I had to implement a bit more complexity in the PlayerController.cs than shown in the course and, with that, I basically fused quite a few part with my own variant because it was just simpler.

Since I’m already building a database of the weapon that includes the possibility of different animations for different weapons under a same type, I’m already building a database of available animations anyway so adding an array of floats for timing the Hit(); is not that much of a trouble for me.

The way I implemented the state in the Animator is, in my opinion, a clever one.

There’s a layer prior to the combat animations involving if the player has a weapon out or not and if not, it pull out the weapon as the combat starts. I won’t cover it here, but let’s just say that, in my project, you can have a weapon out or, in some cases, sheathed.

When it comes to combat animation, my animator only has 5 animation states for combat and I’m using the AnimatorOverrideController method to replace those 5 animations with the various animations based on my weapon database that includes an array of a class I created that hold the reference to an animation clip, the hit timing, the radius & range of the attack, if applicable, so that certain attack animation can, for example, hit more than just the target. I only have to override the Animator controller once when the character (player or AI) is instantiated and, then update it whenever the player change equipment.
The cycle between the attack is, then, handled by simply setting up an integer in the animator (I called it “Combo Length”) which allows the attacks animation states (0 to 4) to transition from one to another while checking if the next animation in the combo is higher than that integer (if it is, then the transition moves back to animation state 0 and skips the remaining states).

As for some (semi-)randomly method being called from within an animation it would indeed be nice if one had some simple way to funnel the animator into using only methods that were meant to be called as animation event handlers…
This seems to be a little oversight from Unity, but maybe it’s for historic reasons and they can’t easily change it or break compatibility with too much already existing code now…

As it is now, one should just check whenever one added new animations into an animator if there are animation states on it that one would have to deal with. In case of a naming clash one would have to rename one’s own method, and one should add one method for the event to be called…
In case of animations that have events that one simply doesn’t need (and one doesn’t want to change the animation track itself), one can easily have an empty dummy method to ignore the event without causing errors in the log…

From your typewriter to Unity’s devs ears!

I often do this with animations that contain FootR, and FootL Animation Events and I don’t feel like dealing with IK or SFX right now. A strong argument could be made, however, for NOT doing this, as the warnings will help you remember to deal with those later if you choose to at a later date. Sort of a regular TODO in Play Mode.

Well, instead of an empty stub method one could of course also add one’s own code to expose unhandled animation events in a more friendly way. Some “evil” approach would be to use some DrawGizmo() or similar thing that only works within the editor and would make builds for a release fail. Or one could define some even to fire and have one centralized catcher for it that might report it in the game’s UI or something…

Technically, the DrawGizmo attribute won’t cause the build to fail…
Of course, if you actually used any Gizmos code within the method with the DrawGizmo attribute, that would break the build.

Privacy & Terms