BinaryCats

IIRC a while ago, it seems to be hit and miss on whether these worked or not. I have a case where I need to play an animation (on track >=1, as track 0 is changing) I have stepped through, and the animation gets added to the correct track. I added break points to check that it wasn't being removed (via clearTrack), When I did this the attachment disappeared!


-however it reappeared the frame after. Although the animation on track 1, does key the attachment on, it is on frame 0, and no keys exist anywhere else.
User avatar
BinaryCats
  • Posts: 1299

Pharan

I had to look up "IIRC". :D

Did you keep track of the value of slot.attachment? (as opposed to what the render looks like)
It may be useful so we know if it's the underlying spine-csharp that's misbehaving, or the spine-unity renderer.
User avatar
Pharan
  • Posts: 5366

BinaryCats

Pharan wrote:I had to look up "IIRC". :D

Did you keep track of the value of slot.attachment? (as opposed to what the render looks like)
It may be useful so we know if it's the underlying spine-csharp that's misbehaving, or the spine-unity renderer.
the physical render of the spine object, As I 'continue' the code after a break point, the next frame* it hit another break point (setting animation on track 0) at which point the attachment was not visible. then, on the next `continue of the code and breakpoint hit' the attachment was visable again.

This repo's 100% of the time, so I will see if I can look at the slot's value

*I assume, previously animation current time was -1. now it is 0f, the length of the animation is also 0f

--
I forgot to say the rest of mny IIRC sentence :rofl: :bang: , iirc a while ago, this was a thing

--
I have isolated the problem:

Here we can see the slots turn off and on (which will get undone)(taken from attachmenttimeline apply)


And then the next frame It plays the previous animation from the code in animation state:
public void Apply (Skeleton skeleton) {
ExposedList<Event> events = this.events;

for (int i = 0; i < tracks.Count; i++) {
TrackEntry current = tracks.Items[i];
if (current == null) continue;

events.Clear();

float time = current.time;
bool loop = current.loop;
if (!loop && time > current.endTime) time = current.endTime;

TrackEntry previous = current.previous;
if (previous == null) {
if (current.mix == 1)
current.animation.Apply(skeleton, current.lastTime, time, loop, events);
else
current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix);
} else {
float previousTime = previous.time;
if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime;
previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null);
When this happens
int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1;


returns 0. So despite The previous animation being 30 frames long, it plays the first frame, which is the frame the attachment get turned on.
User avatar
BinaryCats
  • Posts: 1299

Xelnath

I've been having trouble with these too - so I just started creating two-frame looping animations for all pieces I want turned "on".

Annoying, but it works.
Xelnath
  • Posts: 408

BinaryCats

time = 1f
frames[frames.Length - 1] = 0
frames.Length = 1

And here is the timeline where it turns it on
(coming from play previous)

-- 15 Jun 2016, 08:05 --

so... is this being looked into or?

-- 23 Jun 2016, 16:52 --

I FIXED THIS by clearing the track before playing the animation. However I can not do this due to other reasons, hello@
User avatar
BinaryCats
  • Posts: 1299

Nate

Sorry, Pharan didn't abandon you by choice, he is having major problems with getting his internet reconnected.

I'm having trouble following along. The screenshots are confusing. Can you describe concisely what the problem is?
User avatar
Nate

Nate
  • Posts: 11935

Pharan

Sorry bcats. Coincidentally, repair guys from my ISP arrived about an hour ago to fix stuff. Took them ages.

Just from the logic you've traced, I'm guessing it's a problem with the core Spine animation logic (in spine-csharp).
I'm adding this to the list cases for the AnimationState update either way.

If I understand correctly, the case is:
track 0: a mixing between a previous and next animation is happening. and those have attachment keys.
track 1 or higher: play an animation with attachment keys.

Is that right?

Also, this line is a source of the problem? That's in AttachmentTimeline?
int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1;
User avatar
Pharan
  • Posts: 5366

BinaryCats

That is correct @pharan

Animation 1: turns an attachment A on (20frames)
Animation 2: Does other stuff (20 frames)

Animation 3: Turns attachment A off.

I play animation 1. - keys the attachment on frame 0 - track 0

10 frames in:
Interrupt track 0. with animation 2
play animation 3 on track 1

attachment A stays visable.

-- 24 Jun 2016, 09:37 --

Soo, I isolated the bug, and was planning on sending it to you.


However, I was unsure how to export just a scene rather than the whole project. So I made a new project with the latest stuff. And it doesn't happen! It is either the old runtime, or the modifications to that runtime that has caused this :sweat:

-- 24 Jun 2016, 09:53 --

:doh: :angel: :clap: it is not our changes. this must have been fixed in the newer versions of spine
User avatar
BinaryCats
  • Posts: 1299

Nate

There is no bug? My favorite kind of bug! :clap:
User avatar
Nate

Nate
  • Posts: 11935

BinaryCats

Nate wrote:There is no bug? My favorite kind of bug! :clap:
there was a BUG! IN THE past, you got lucky (punk) with new runtimes

-- 06 Jul 2016, 14:26 --

Nope. I was wrong! it still happens.

What do you need from me to help investigate this issue.

I cant get the bug to happen when break pointing now, so I'm stuck with debug logs,


Here I have one animation running, Attack_Knife_intro (60 frames track 0). Then I interrupt that animation (I damage the character) and it plays an animation TakeHit_Upper ( 30 frames track 0). On the same frame, I play animation Attachment_Knife_Off (1 Frame, track 1)

Attack_Knife_intro Turns Attachment knife_a ON, in slot WeaponA - first frame.
TakeHit_Upper DOES NOT ALTER slot WeaponA, At all.
Attack_Knife_intro Turns Attachment knife_a OFF, in slot WeaponA - first frame.

The above image shows the following, the current anuimation playing (____anim____) and any time an attachment is set, in the format slot: current image to new image currenttime

-- 06 Jul 2016, 14:45 --

I have repo'ed it in a project using latest unity runtimes and goblin-mesh (altered)

It uses the script animationstuff to handle playing of animations,

https://drive.google.com/file/d/0B5ElW0WUt28eMG1UWlgtTGFoUXM/view?usp=sharing

-- 06 Jul 2016, 14:54 --

its in the goblin scene

-- 06 Jul 2016, 16:50 --

I have narrowed down the problem to
previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null);
line 119 Animation state. bug is fixed by removing it.

However, I am unsure why this line even exists. All this does is play the previous animation.

Take the attachment time like (only visable one causing a problem)
public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.

int frameIndex;
if (time >= frames[frames.Length - 1]) // Time is after last frame.
frameIndex = frames.Length - 1;
else
frameIndex = Animation.binarySearch(frames, time, 1) - 1;

String attachmentName = attachmentNames[frameIndex];
skeleton.slots.Items[slotIndex]
.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
}
for any timelines' frame < current time, it will apply it. However, this does not account for the current state of the attachment. I.e. if another track has turned it off.

I have noticed that you don't check the timeline's frame time against lastTime. Is this so it never misses an attachment keyframe?

-- 06 Jul 2016, 17:05 --

I changed the code to:
public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.

int frameIndex;
if (time >= frames[frames.Length - 1]) // Time is after last frame.
{
if (lastTime >= frames[frames.Length - 1]) // you missed your chance
return; //now bugger off
frameIndex = frames.Length - 1;
}
else
frameIndex = Animation.binarySearch(frames, time, 1) - 1;

String attachmentName = attachmentNames[frameIndex];
skeleton.slots.Items[slotIndex]
.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
}
It works, but I am unsure what knock-ons it will have.
User avatar
BinaryCats
  • Posts: 1299

Nate

We recently changed back to always setting the attachment rather than using lastTime. This allows 2 tracks to key attachment changes and the higher track wins since it is applied last. If we checked lastTime to trigger the attachment change then the most recent track to set the attachment wins, which doesn't make sense as the higher track wins for all other types of keys.
User avatar
Nate

Nate
  • Posts: 11935

yookunka

We recently changed back to always setting the attachment rather than using lastTime. This allows 2 tracks to key attachment changes and the higher track wins since it is applied last. If we checked lastTime to trigger the attachment change then the most recent track to set the attachment wins, which doesn't make sense as the higher track wins for all other types of keys.
I'm sorry I don't get your comment well. So do I need to turn an attachment off at a beginning of every animation if the animation doesn't need it? This isn't ideal at all because I need to modify all the animation if I add a new attachment. I want attachment's current state (on or off) to remain unless a higher track changes it when animation changes.
User avatar
yookunka
  • Posts: 16

Pharan

@yookunka
I think yours is a separate issue though the recent changes above does solve part of the issue, so it's cool.

It certainly wouldn't be ideal if the setup pose defines the slot to have no attachments active, and you have a lot of animations. Muddying up the dopesheet is never a good workflow.

Clarification: It's technically the slot's state and not the attachment's (this is why the key icon is next to the slot in Spine editor).
Setting the slot's attachment to null means "hiding an attachment".

Also, Spine's behavior is that it leaves things alone (bones, slots, draw order), unless an animation keys it. So its state actually already "remains" unless something else changes it. The fact that your higher tracks (or any of the other tracks) never key the slot back to empty is why the attachment doesn't disappear. This is just by default and can be changed with code.

You clear a slot by saying slot.Attachment = null. You return it to setup pose by saying slot.SetToSetupPose(). You could conceivably do that whenever an animation starts or ends (using the existing animation callbacks). Spine-Unity actually has some extra methods that make it easier to find the right items to reset.
So, normally, if you're animating on a single track, this would be as simple as resetting slot/bone states every start of an animation, or resetting animated item states every frame during transitions.

If you're on multiple tracks, it sort of requires case-by-case handling. But this is more manageable if certain slots are sort of "managed" by a track: For example, if animations on a specific track key that slot's attachment (or turn it on or off), or leave it alone for a lower track to animate.

If you could describe your setup in more concrete terms, we may have something simple to recommend. I think opening a new topic would be best. :)
User avatar
Pharan
  • Posts: 5366

yookunka

@Pharan

Thank you for your detailed explanation. I already posted and described my situation at the following thread, but @BinaryCats pointed me this thread then I got confused after reading through all the comments :D

http://esotericsoftware.com/forum/Libgdx-Attachment-Bug-Fix-Proposal-6614

Could you take a look at my comment in the above thread and give some advise please? :$
User avatar
yookunka
  • Posts: 16

Pharan

Oh, so you're not a Spine-Unity user.
That complicates it just a tiny bit. But it's fine. We can advise you on the other topic.
User avatar
Pharan
  • Posts: 5366

BinaryCats

Nate wrote:We recently changed back to always setting the attachment rather than using lastTime. This allows 2 tracks to key attachment changes and the higher track wins since it is applied last. If we checked lastTime to trigger the attachment change then the most recent track to set the attachment wins, which doesn't make sense as the higher track wins for all other types of keys.
So what is the solution here?

Maybe it should just ignore the attachment timeline when mixing (I assume that's why you are doing previous.apply)?, or it should not apply any keys before the mixing happened?
Nate wrote:If we checked lastTime to trigger the attachment change then the most recent track to set the attachment wins, which doesn't make sense as the higher track wins for all other types of keys.
I am unsure what you mean by the most recent track to set the attachment. If you mean that the most recent attachment-key to be fired( regardless of what track it is on ) to be applied, this is what you want... isn't it?
User avatar
BinaryCats
  • Posts: 1299

Nate

BinaryCats, honestly I'm still not sure what problem you are having. I need it to know 1) what you are doing, 2) what actually happened, and 3) what you expected to happen. If you can do that as concisely as possible that would help. If you leave out any of those, it is hard to guess at the problem and then confusing if I guess wrong.
BinaryCats wrote:I am unsure what you mean by the most recent track to set the attachment.
Track 0 is applied, then track 1. If both key an attachment, track 1 will be the most recent track to set the attachment.
User avatar
Nate

Nate
  • Posts: 11935

BinaryCats

Nate wrote:BinaryCats, honestly I'm still not sure what problem you are having. I need it to know 1) what you are doing, 2) what actually happened, and 3) what you expected to happen. If you can do that as concisely as possible that would help. If you leave out any of those, it is hard to guess at the problem and then confusing if I guess wrong.
BinaryCats wrote:I am unsure what you mean by the most recent track to set the attachment.
Track 0 is applied, then track 1. If both key an attachment, track 1 will be the most recent track to set the attachment.
....
I Have provided a download link to a unity project and provided the spine file which has isolated the bug.

I will try to make it clearer when I get home (although I think I have outlined it in its simplest form)

-- 07 Jul 2016, 19:49 --

If you have an animation playing that keys on an attachment in a slot, then interrupt that animation (by setting an animation on the same track) and simultaneously play an animation on a different animation that keys off THAT SAME ATTACHMENT, IN THE SAME SLOT. The attachment will turn off for ONE FRAME, then the next frame That attachment, due to mixing, will be keyed back on. Even if it happened a long time ago.

The attachment should remain off. Attachment keys outside of the mixing time SHOULD NOT have a say on if a slot is turned on or off.

-- 07 Jul 2016, 19:52 --

I think: One Frame Attachment Animations explains it pretty well in a step by step
User avatar
BinaryCats
  • Posts: 1299

yookunka

The attachment should remain off. Attachment keys outside of the mixing time SHOULD NOT have a say on if a slot is turned on or off.
Yeah, I agree with BinaryCats as I have the same problem.
User avatar
yookunka
  • Posts: 16

Pharan

Wait. So your steps are:
//have mixing.
state.SetAnimation(0, "key a slot on", false);
//wait 0.5 seconds or something
state.SetAnimation(0, "interrupts the first animation", false);
state.SetAnimation(1, "key the slot off", false);
Is that correct? And the end result SHOULD be that the slot is empty.
But you end up with it being nulled for one frame and then coming back on?

-- 08 Jul 2016 8:07 am --

You said a bunch of things about removing a line from AnimationState or changing AttachmentTimeline.Apply. It's not clear what your current state is at the moment.

-- 08 Jul 2016 8:36 am --

using UnityEngine;
using System.Collections;
using Spine;
using Spine.Unity;

public class OneFrameRepro : MonoBehaviour {

[SpineAnimation]
public string keyOn, keyOff, somethingElse;

IEnumerator Start () {
var skeletonAnimation = GetComponent<SkeletonAnimation>();
var state = skeletonAnimation.state;

state.Data.defaultMix = 0.5f; // 0.5 second crossfade.
state.SetAnimation(0, keyOn, true); // 1 second animation of the slot being keyed with an attachment.
yield return new WaitForSeconds(0.3f);
state.SetAnimation(0, somethingElse, true); // interrupt first animation. just translates the root bone.
state.SetAnimation(1, keyOff, true); // 1 second animation of the slot being keyed null.

// slot is empty at the end of the animations. nothing unusual happened.
}
}
I did the above and the result was that it nulled the slot successfully. And no one-frame bug.
Used the latest spine-unity.

There must be something else going on.

@yookuna. Your issue is not the same. Your issue is from expected behavior. Please see the other topic.
User avatar
Pharan
  • Posts: 5366

BinaryCats

............
Like have you even looked at the project I have provided?

And removing a line was only the determination of what was causing it.

It's working in your example because your 'turn off' animation is longer than the mix time. I have repeatedly said and it's in the title "one frame animation".

This is getting frustrating now. Maybe we can Skype this out.

And his issue is the same issue
User avatar
BinaryCats
  • Posts: 1299

Pharan

It was pretty confusing.
I didn't even know where to look in the project.

Anyway, I found it.
The "bug" is that this is how AnimationState and Animation.apply is currently designed to behave.
I'm drawing it so Nate understands the problem at a glance.


I can't really offer much on my end to actually solve the problem for your setup except to hack away at AnimationState and AttachmentTimeline.apply so it suits your needs, or inconveniently key the attachment off in every animation that possibly comes after the animation that turns it on.

In the larger scheme, we know you particularly have many issues with AnimationState and Nate is aware of it too, as AnimationState in all runtimes has known issues and is due for updated logic: AnimationState improvements · Issue #621 · EsotericSoftware/spine-runtimes · GitHub

These use cases are informative.
User avatar
Pharan
  • Posts: 5366

BinaryCats

the bug goes deeper than that.


not as pretty but:

here shows that tracks which end and have keys during the mix period, the slot's attachment gets changed back.

As for your suggestion of hacking around it, it is possible but it is causing a lot of other bugs. The best way for now is to clear the queue before playing track1, but we rely on queues quite a lot, the if statement is 5 compares log atm.

Another way of doing it is to ensure the 'off animation' is longer than the mix. However with the case above, that isn't really an option.

We also cant key everything off, in all other animations.
User avatar
BinaryCats
  • Posts: 1299

Nate

Sorry Bcats, I'm not able to make sense of your image. I don't know what the red lines are or what the animations are doing.

Pharan's image makes sense. The previous animation continues to be applied as it is mixed out, so its attachment change keys are applied after the animation on track 1 changes the attachment. yookunka has the the same scenario except the slot's attachment is being set via code rather than a 1 frame animation.

To be clear, this is not a bug. The current behavior is that attachment keys are applied during mixing. Issue #621 has a potential solution (#12) going forward. If you want a code change solution for now, that is entirely possible.

What you want is to avoid applying any attachment timelines for the previous animation during mixing. AnimationState has this line to apply the previous animation:
previous.animation.apply(skeleton, previousTime, previousTime, previous.loop, null);
If you look at apply, it's very simple:
public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array<Event> events) {
if (loop && duration != 0) {
time %= duration;
if (lastTime > 0) lastTime %= duration;
}
for (int i = 0, n = timelines.size; i < n; i++)
timelines.get(i).apply(skeleton, lastTime, time, events, 1);
}
So, let's write our own apply function and call it instead of the previous.animation.apply line, above:
applyPrevious(previous.animation, skeleton, previousTime, previousTime, previous.loop, null);
...
private void applyPrevious (Animation animation, Skeleton skeleton, float lastTime, float time, boolean loop,
Array<Event> events) {
float duration = animation.getDuration();
if (loop && duration != 0) {
time %= duration;
if (lastTime > 0) lastTime %= duration;
}

Array<Timeline> timelines = animation.getTimelines();
for (int i = 0, n = timelines.size; i < n; i++) {
Timeline timeline = timelines.get(i);
if (timeline instanceof AttachmentTimeline) continue; // No attachment changes.
timeline.apply(skeleton, lastTime, time, events, 1);
}
}
User avatar
Nate

Nate
  • Posts: 11935

BinaryCats

Sorry, the red lines are attachment keys, for the same slot. - its to show you cant just change the length of the animation to be longer than the mix period and the black line is a few px to the left, it should be at the end of the orange. I'm not an artist.


I had something similar to that code at one point. But I don't like changing the runtimes, we already have done to add root motion, and its annoying as ___ when it come to updating the runtimes.


I disagree with it not being a bug. Or atleast, I disagree with how the attachment runtime behaves. In my eyes, the latest state of the slot should persist, no matter the track that changes it. And it should only change when a more recent attachment key is come across, on any tracks.

Take this example, where the rows are tracks, and colours are track entries, and red bars are Attachment keys (labeled with letters)



At the thin black line, what attachment would you expect to be visible? Am I correct that a would be visible? I would have thought c should be visible as it is the most recent change of that slot.
User avatar
BinaryCats
  • Posts: 1299


Return to Unity