• Unity
  • Changing skin with event causing Unity to freeze

Hi there,

Half way through an animation I want to change skins (dynamically chosen), so I created an event called "Change Skin". When I receive this event I run the below method with the equipment I want:

void UpdateSkin(params EquipmentDataBase[] equipmentData) {
    _loadoutSkin = _loadoutSkin ?? new Skin("Loadout");
    _loadoutSkin.Clear();

foreach (EquipmentDataBase equipment in equipmentData) {
    if (equipment == null) { continue; }

    _loadoutSkin.Append(_spineSkeleton.Data.FindSkin(equipment.AnimationName));
}

_spineSkeleton.SetSkin(_loadoutSkin);

_spineSkeleton.SetSlotsToSetupPose();
_spineAnimationState.Apply(_spineSkeleton);
}

However when I do this, Unity completely freezes. The UpdateSkin method works fine outside of the event. I assume it's a StackOverflow that I never receive, but I was wondering if I'm missing something? Is there any problem with swapping a skin using an Event?

Thanks!

Related Discussions
...

Oh boy. This is expected.
In this case, it would solve it if you do this:
Replace:

_spineSkeleton.SetSlotsToSetupPose();
_spineAnimationState.Apply(_spineSkeleton);

with

_spineSkeleton.SetSlotsToSetupPose();
_spineAnimationState.Event -= YourHandlerMethod;
_spineAnimationState.Apply(_spineSkeleton);
_spineAnimationState.Event += YourHandlerMethod;

The other option is to queue the skin change method to be called the next Unity update frame instead of having it as a direct callback.

The problem was that _spineAnimationState.Apply causes all captured events that frame to trigger. So if you call that as a response to the callback, you'll get infinite recursion (and of course, Unity freezes until the eventual stack overflow.)

There are other solutions to this problem too if this doesn't quite work for you. The above will still call any other events captured that frame more than once, as it would have anyway even if your skin change wasn't in the callback.

Ah, OK, makes sense! I don't want any events triggering twice, so I solved it in my event listener:

int _lastEventFrame;
readonly List<string> _eventsTriggeredThisFrame = new List<string>();

void OnEvent(TrackEntry trackEntry, Event e) {
    string eventName = e.Data.Name;

if (_lastEventFrame == Time.frameCount) {
    if (_eventsTriggeredThisFrame.Contains(eventName)) { return; }
} else {
    _lastEventFrame = Time.frameCount;
    _eventsTriggeredThisFrame.Clear();
}

_eventsTriggeredThisFrame.Add(eventName);

switch (eventName) {
    case "Change Skin": UpdateSkin(_activeEquipment);  break;
    case "Sound": GameplayAudioManager.Play(e.String, Agent.transform); break;
}
}