- Modifié
Choosing to Fire Events or not during mix with code?
Is there any way to set something on the TrackEntry or anything else that would allow us to mix events for that 1 specific call to SetAnimation?
Use case:
Attack A > Attack B, at 90% in a HitBox event is dispatched to create a collider with Mix .2, so this normally gets mixed out every time unless we set it to always fire. We currently work around this by ensuring all animations have their events before we call SetAnimation which is always 85% into the animation.. the artist knows this.
"The new AnimationState in 3.5, it gives you an option of how much percent of the transition should happen before it starts ignoring events."
This works great until:
Player gets hit at 60% and we now don't want any events from Attack A to fire.. instead all events should be halted and the player animates to GetHit animation.
Does TrackEntry eventThreshold
do what you want?
- Modifié
This actually works great to default to 1f and now correctly plays events even if they're on the 2nd to last frame.. which is helpful.
I may have some logic wrong though.. If I am in the middle of Attack A and GetHit plays, the event in Attack A still isn't playing (which is what I want but not what I expect).
So Attack A executes which at .85% in, if queued attack is flagged.. flows into Attack B and everything works as expected.
If the player is hit during Attack A, the spine event somehow gets discarded which is great.. but how is that possible if the eventThreshold is 1f? I'd expect it to play all events still.
When I enter my FSM node for an animation, this is what it looks like.
All that's really relevant is OnExecute is called once when my state is entered and OnUpdate is well Update. At exit time on non looping animations like attacks, I transition out automatically but if the player is hit, then it immediately calls a GetHit animation which I would think would cause a mix to it and its eventThreshold is 1f.. I expected to have to set this to 0f to work correctly.
namespace NodeCanvas.Tasks.Actions
{
[Category("Picklefeet")]
public class PlayAnimationSpine : ActionTask<Transform>
{
private SkeletonAnimation skeletonAnimation;
private Spine.AnimationState spineAnimationState;
private Spine.TrackEntry trackEntry;
private Blackboard bb;
[RequiredField]
public string animationClip;
[SliderField(0, 4)]
public int layer = 0;
public bool loop = false;
public bool isAttack;
public float exitTime = .85f;
public float timeScale = 1f;
//input is locked while this animation is playing
public bool locksInput;
public BlendType blendType = BlendType.Set;
public enum BlendType
{
Set=1,
Add=2,
}
//0% means no events from the "from" anim will fire, 100% means mix all events from the "from" anim during transition
public float eventThreshold = 1f;
protected override string info
{
get { return "Anim " + animationClip.ToString(); }
}
protected override string OnInit()
{
if (skeletonAnimation == null)
{
skeletonAnimation = agent.GetComponentInChildren<SkeletonAnimation>();
spineAnimationState = skeletonAnimation.state;
bb = agent.GetComponent<Entity>().bbAnimation;
}
return null;
}
protected override void OnExecute()
{
if (blendType == BlendType.Set)
{
trackEntry = spineAnimationState.SetAnimation(layer, animationClip, loop);
}
else if (blendType == BlendType.Add)
{
trackEntry = spineAnimationState.AddAnimation(layer, animationClip, loop, 0f);
}
trackEntry.TimeScale = timeScale;
trackEntry.EventThreshold = eventThreshold;
//if (isAttack)
//{
bb["meleeAttack"] = false;
bb["attackChain"] = false;
//}
bb["animNormalizedTime"] = 0f;
bb["attackNormalizedTime"] = 0f;
if(locksInput)
{
bb["inputLock"] = true;
}
}
protected override void OnUpdate()
{
float normalizedTime = trackEntry.TrackTime / trackEntry.TrackEnd;
if (isAttack)
{
bb["attackNormalizedTime"] = normalizedTime;
if (normalizedTime < .25f)
{
bb["inputLock"] = true;
}
else
{
bb["inputLock"] = false;
}
}
bb["animNormalizedTime"] = normalizedTime;
//if this animation doesn't loop, immediately end and transition out when completed
if (!loop && normalizedTime >= exitTime)
{
//if this is an attack and we go past the point of no return for a chain attack, ensure everything is reset
if (isAttack)
{
bb["meleeAttack"] = false;
bb["attackChain"] = false;
}
if(locksInput && (bool)bb["inputLock"] == true)
{
bb["inputLock"] = false;
}
EndAction(true);
}
}
}
}
I'm not super clear on when your event fires and what you are using for eventThreshold
. I understand you play animation A, then at 85% of A's duration you play B. You have some event keyed in A? When?
If A is interrupted by animation Z and you do not want A to fire the event as it mixes out, set eventThreshold
to 0 (or don't set it, 0 is the default). If you do want A to fire the event it is mixing out, set eventThreshold
to 1. Keep in mind though that if Z happens early enough in A's playback, A may be mixed out completely before its event fires.
Nate a écritKeep in mind though that if Z happens early enough in A's playback, A may be mixed out completely before its event fires.
Ah, ok this may be what's happening.. I can't seem to get any tests that actually get the event to fire from Attack A but I'll trust you on it. I'm attempting to spam attacks and get hit really late into the attack, but can't manage to get the event to fire which is at frame 34 into a 35 frame animation.
Right now... this feature of allowing events to fire "works" for us though, lol We were losing events when transitioning from Attack A to B but they now all work great. I'm super confused why that works but Getting Hit completely truncates the events.
30 Nov 2016, 11:40
Edit: I just set mix from Attack A to GetHit .5 and I think that's exactly what's happening. I think I was mistaking .2f default mix time being 20% of the animation, not .2f in real time.
Not sure what's going on, but I'm glad it works. 8) Let us know if you find a bug! It's best if you can modify an example to show the problem.
Just to finalize this idea.. If you play GetHit and get its track, that has no impact on the Attack A's mixing right? Attack A would continue to mix out based on its eventThreshold? You would have to somehow detect getting hit, adjust the TrackEntry related to Attack A, and then call SetAnimation on Get Hit..
Sorry, I don't understand. Can you try explaining again?
pseudocode
If you use SetAnimation("Attack") , trackEntry.EventThreshold = 1f; but then you get hit.. it would call SetAnimation("GetHit"), trackEntry.EventThreshold = 0f
Since attack was already called with an event threshold of 1f it would still play all its events right? When the player gets hit.. you'd need to store like a TrackEntry lastTrack variable and set its EventThreshold to 0f; before calling Get Hit? Would this be too late to do any mixing changes?
This is very easy to do in my FSM.. I can simply set the last known TrackEntry only when transitioning to my Get Hit state, assuming my logic is correct. I can't seem to get a live working case of this in my game since .2 mix time is so small it may simply be bad timing with me trying to break it. I hate to call it good and release the game with it broken though
Sorry.. I'm horrific at explaining things, known problem lol.
Majicpanda a écritSince attack was already called with an event threshold of 1f it would still play all its events right?
Yes, if the eventThreshold
for Attack is 1, when Attack is mixed out it will fire all events up until the mix duration is reached, after that Attack is gone.
Majicpanda a écritWhen the player gets hit.. you'd need to store like a TrackEntry lastTrack variable and set its EventThreshold to 0f; before calling Get Hit?
You should be able to do something like:
TrackEntry entry = state.setAnimation(0, "GetHit", false);
if (entry.mixingFrom != null) entry.mixingFrom.eventThreshold = 1;
This sets eventThreshold
to 1 for ANY animation that is being mixed out when GetHit is set.
You guys give exquisite support, going to have to send a bottle of scotch after this is all over.
Your logic fixed everything.. didn't even occur to me to check for a TrackEntry on the current one, worked flawlessly setting a simple lastEventThreshold float on my animation states and for Get Hit, set to 0f... good as gold. I set the mix time from Attack A to Get Hit as 1f to really play the heck out of it and testing 1f for my float threshold definitely played the event and 0 never did.
Thanks for hanging in there with me on this one.
Fantastic! :party:
I just realized since MixDuration is in seconds and .2f default, I need to change my non looping animation exit time code to:
if (!loop && trackEntry.TrackTime >= trackEntry.TrackEnd - trackEntry.MixDuration)
I was transitioning out at 80% into the animation which was ".2f" I thought, but this would instead mix out exactly however the animation is set up and ensure all events get played and not mixed out incorrectly. Before, if I had started a mix at 80% in and the animation is 2 seconds.. it would totally miss the last .2ish seconds of events and the animation correct?
:wait:
That sounds right. If your animation is 2 seconds, and you start mixing out at 1.6 with a mix duration of 0.2, then your animation is completely mixed out by 1.8.
Ok and to further end this.. I just noticed animations with like .5f duration and default mix .2f made me change this to:
float desiredMixDuration = trackEntry.TrackEnd * .2f;
Am I weird in wanting all my animations to have a mix duration in % into an animation opposed to exact time?
30 Nov 2016, 16:17
This gives me positive results:
float desiredExitTime = trackEntry.TrackEnd * .85f;
if (!loop && trackEntry.TrackTime >= desiredExitTime)
{
//exit animation state and begin new transition to another SetAnimation
}
Actually this is correct... you would want mix to always be a short duration, but where we EXIT and begin the transition animation should be very carefully controlled by us like I've done above.
Yes, it's kind of weird to use percent. You can use
entry.animation.duration * percent
to convert it into time. Using trackEnd
as you have done will also work, but only for non-looping animations. Non-looping animations have a trackEnd
of animation.duration
while for looping animations trackEnd
is the max float value (essentially forever).
This code from a few posts back doesn't make sense:
if (!loop && trackEntry.TrackTime >= trackEntry.TrackEnd - trackEntry.MixDuration) {
trackEntry.MixDuration
is the time for that entry to mix in. You'd need to know the mix duration of the next animation you want to play:
if (!loop && trackEntry.TrackTime >= trackEntry.Animation.Duration - nextTrackEntry.MixDuration) {
Note I also changed it to use animation.duration
.
I think I mixed up mix duration with exit time and what should be done. Exit time in Mecanim is specified as a float percent and defaults to .1 transition duration as a fixed duration. Unchecking makes it a percent but yes, that does seem like it would not be the norm.
Setting .2f default for everything works pretty much as expected and using my last posts EXIT time code to mimic Mecanim's Exit Time and "has exit time" on my state controller finalizes my transition off Mecanim.
Though no one seemed to care.. I did figure out an easy way to apply all of this to Mecanim by using behaviors on each state even with Spine Animation lol. Oh well.. NodeCanvas is pretty amazing
Thanks again, all is well.