• Runtimes
  • 3.5+ Mixing carries over multiple animations...

Related Discussions
...

...regardless of the set mix.

(this is using unity mix UI, so move this is you must, however I think it is more of a runtime design)

We have 3 animations:

Idle, Walk, and Attack

Which are played in quick succession:

Image removed due to the lack of support for HTTPS. | Show Anyway

The mix time between Idle -> Walk = 0.2. The Mix time betweenIdle->Attack = 0, The Mix Time between Walk->Attack = 0.

Because the animations are played in quick succession, the ApplyMixingFrom function shows that Attack Is mixing from Idle with a entry.mixDuration = 0.2 (entry = attack)


Changing line

entry.mixDuration = from.mixDuration;

to

entry.mixDuration = data.GetMix(newFrom.animation, entry.animation);

in UpdateMixingFrom (TrackEntry entry, float delta, bool canEnd) in animationstate.cs works, however I am not sure this is the optimal fix

Does the problem occur in Skeleton Viewer? Can you provide the JSON and steps to reproduce in Skeleton Viewer?

well the code is the same soo.. I would say it would so. Skeletonviewer isn't exactly a good place to test stuff like this, you cant set up timings, or queues of events to fire.

I'm unsure how I can break this down any further.

0s: Play animation A
1s Play animation B
1.1s Play animation C

Animation A->B mix = 0.2s
Animation B->C mix = 0s
(Animation A->C mix = 0s)

Place a breakpoint inside ApplyMixingFrom and observe it comethrough C mixing from A with a 0.2s mixDuration

I'd never get anything done if, for every forum post, I spent an hour setting up animations just to hope I can run into the exact same problem someone is having. Such a shot in the dark isn't efficient. If you can't repro in SV, how about providing the JSON and set/addAnimation calls to repro?

Sure here is a unity project.

I assume you have visual studio. Put a breakpoint on line 128 in animation state, with the condition entry.animation.name == "walk3"

default mix = 0.

walk -> walk2 mix = 0.2.

(ignore the ik bug names, I was trying to get a repo)

https://www.dropbox.com/s/lxgy52gzpk3s627/SpineIK%20Bug.zip?dl=0

open the goblins scene in SpineIK Bug\Spine IK Spinning\Assets\Examples\Other Examples

Pharan, can you take a look when you get a chance? :heart:

I'll check it out.


Link seems broken.

Link works fine for me.

Case:
Basically, it's ANY three animations A, B and C.
You play them in sequence quickly so that they are all in the middle of a crossfade/mix.
A->B->C
(where C.mixingFrom == B and B.mixingFrom == A😉

In AnimationState.updateMixingFrom (called from update), AnimationState handles the case where B->C mix ends before the A->B mix does.
In this case, we need to dispose of trackEntryB, and the new Track setup needs to be trackEntryC.mixingFrom == trackEntryA (a linked-list remove).
in other words
A->B->C
turns into
A->C

Code:
The block of code that handles it looks like this:
entry is trackEntryC, from is trackEntryB, newFrom is trackEntryA.

TrackEntry from = entry.mixingFrom;
if (from == null) return;

if (canEnd && entry.mixTime >= entry.mixDuration && entry.mixTime > 0) {
   queue.End(from);
   TrackEntry newFrom = from.mixingFrom;
   entry.mixingFrom = newFrom;
   if (newFrom == null) return;
   entry.mixTime = from.mixTime;
   entry.mixDuration = from.mixDuration;
   from = newFrom;
}
// [...]

The question is, what should happen?
In the current setup, when you dispose trackEntryB, you get its mixTime and mixDuration. (trackEntryB's mixDuration came from A->B mix data)
Because the resulting Track setup actually looks like trackEntryA->trackEntryC, @bcats thinks you should get the mixDuration of A->C from mix data and use that.

Thanks Pharan! :makeup:

BinaryCats wrote

:wonder: that's what I said!

Riiight. :think: Also, I wanted JSON and set/addAnimation calls and got a Unity project!

I can reproduce like this:

state.setAnimation(0, "walk", true);
state.addAnimation(0, "jump", true, 0.25f).mixDuration = 4;
state.addAnimation(0, "run", true, 1).mixDuration = 0;

There's also a similar test case where the run mixDuration is 1 second. This is what the track entries look like:

walk
then: jump, 4s mixDuration
then, during walk -> jump mix: run, 1s mixDuration

walk.mixingFrom = null
jump.mixingFrom = walk
jump.mixDuration = 4
run.mixingFrom = jump
run.mixDuration = 1

jump to run completes:

walk.mixingFrom = null
run.mixingFrom = walk
run.mixDuration = 4

I noticed in the code Pharan pointed out that TrackEntry#mixAlpha is not copied like mixDuration and mixTime. However, I still see snapping when jump completes. My conclusion is that it never makes sense to remove an entry from the middle of the mixingFrom linked list.

Let's say walk keys 0, jump keys 100, and run keys 200. walk to jump goes 0 to 100 and jump to run goes from that to 200. If jump to run completes and we remove jump, we are left with walk to run going 0 to 200. If walk to jump was 50% then walk to jump should have been 50, but after removing jump 50% becomes 100. This causes bones to snap when jump completes.

If we don't remove jump then it works correctly. walk to jump keeps mixing from 0 to 100, then run is applied at 100% (since the jump to run mix has completed). This example is a bit dumb because most likely run keys everything that walk and jump key, so completely overwrites their poses. However, it is possible to mix animations that don't key the same values, so it's possible that walk to jump would be changing something run doesn't.

If removing from the middle of the linked list didn't cause snapping, it would be nice to avoid a long linked list when repeatedly interrupting long mix durations. It seems this optimization isn't possible though. I've committed the changes to spine-libgdx and they'll make their way to the other runtimes soon.
[libgdx] Only remove mixingFrom entries from the end of the list.@99ca32d

Nate wrote

Riiight. Also, I wanted JSON and set/addAnimation calls and got a Unity project!

You seemed stressed. I thought providing pseudo code would have been enough. because unfortunately, I don't know all of the runtimes functions off by heart, and how they differ between languages. So I provided a unity project where you could see the code, and where it went wrong in an isolated environment. Rather than have you blame something else about the project.

I Specifically didn't provide more details about animations A,B and C because I would have thought it would be obvious they could be any animations.

I did not know if add animation rather than setanimation recreated the bug, hence why I said:

BinaryCats wrote

0s: Play animation A
1s Play animation B
1.1s Play animation C

I'm unsure how this is not obvious ¯_(ツ)_/¯

I am trying the fix now


confirmed works

BinaryCats wrote

:wonder: that's what I said!

I used a better font.

Pseudo code wasn't enough, the problem was complex enough I needed to be able to reproduce it to work on it. That's why I asked for JSON and animation calls, which are the simplest way to reproduce and would ensure that what I am looking at is indeed the problem you are having. It's very common when helping users to have them send their whole project. This takes a lot of time to setup and dig through, and often the problem ends up being with the users code. It's also common for users to not provide enough information to reproduce the problem. It's time consuming to chase, guessing at their setups, skeleton data, etc and then hoping we see the exact same problem so we can fix it. By reducing things to their simplest form, users help us help them. Often they can find answers to their questions themselves.

Yes, it can be stressful. 🙂 For Spine, if I get too much information (whole projects) then it usually gets backburnered, as I have a lot of things to do. If I don't get enough information, I have to ask for the right info which makes Nate angry! 😉 For my OSS, too much or too little information usually means no response at all. I'm happy to help people with problems, but if they don't put effort into efficient use of my time, I'm less likely to spend my time. I never instruct those who aren't full of passion, and I never enlighten those who aren't struggling to explain themselves. If I show you one corner and you can't show me the other three, I'll say nothing more. -Confucius

Aaanyway, I'm glad the fix works! AnimationState is a beast.