• Bugs
  • [Unity] incorrect Timeline flip state and code fix

Related Discussions
...

The current code use OnGraphStop() to restore flip state but OnGraphStop() will also be called when Timeline is just paused. The follow is a fix. It use 'stopped' event from playableDirector so that even pausing the timeline won't revert the flip state.

using System;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;

using Spine.Unity;

namespace Spine.Unity.Playables {
   public class SpineSkeletonFlipMixerBehaviour : PlayableBehaviour {
      float originalScaleX, originalScaleY;
      float baseScaleX, baseScaleY;

  SpinePlayableHandleBase playableHandle;
  bool m_FirstFrameHappened;

    public override void ProcessFrame (Playable playable, FrameData info, object playerData) {
     playableHandle = playerData as SpinePlayableHandleBase;

     if (playableHandle == null)
        return;

     var skeleton = playableHandle.Skeleton;

     if (!m_FirstFrameHappened) {
        originalScaleX = skeleton.ScaleX;
        originalScaleY = skeleton.ScaleY;
        baseScaleX = Mathf.Abs(originalScaleX);
        baseScaleY = Mathf.Abs(originalScaleY);
        m_FirstFrameHappened = true;
     }

     int inputCount = playable.GetInputCount();

     float totalWeight = 0f;
     float greatestWeight = 0f;
     int currentInputs = 0;

     for (int i = 0; i < inputCount; i++) {
        float inputWeight = playable.GetInputWeight(i);
        ScriptPlayable<SpineSkeletonFlipBehaviour> inputPlayable = (ScriptPlayable<SpineSkeletonFlipBehaviour>)playable.GetInput(i);
        SpineSkeletonFlipBehaviour input = inputPlayable.GetBehaviour();

        totalWeight += inputWeight;

        if (inputWeight > greatestWeight) {
           SetSkeletonScaleFromFlip(skeleton, input.flipX, input.flipY);
           greatestWeight = inputWeight;
        }

        if (!Mathf.Approximately(inputWeight, 0f))
           currentInputs++;
     }

     if (currentInputs != 1 && 1f - totalWeight > greatestWeight) {
        skeleton.ScaleX = originalScaleX;
        skeleton.ScaleY = originalScaleY;
     }
  }

  public void SetSkeletonScaleFromFlip (Skeleton skeleton, bool flipX, bool flipY) {
     skeleton.ScaleX = flipX ? -baseScaleX : baseScaleX;
     skeleton.ScaleY = flipY ? -baseScaleY : baseScaleY;
  }

  // NOTE: Cannot use this because Pause will also trigger.
  /* 
    public override void OnGraphStop(Playable playable)
    {
        m_FirstFrameHappened = false;

        if (playableHandle == null)
            return;

        var skeleton = playableHandle.Skeleton;
        skeleton.ScaleX = originalScaleX;
        skeleton.ScaleY = originalScaleY;
    }
  //*/

  // NOTE: instead, subscribe to stopped event 

  PlayableDirector director = null;

  public override void OnPlayableCreate(Playable playable)
  {
     director = playable.GetGraph().GetResolver() as PlayableDirector;
     director.stopped += OnTimelineStopped;
  }

  public override void OnPlayableDestroy(Playable playable)
  {
     director.stopped -= OnTimelineStopped;

     base.OnPlayableDestroy(playable);
  }


  void OnTimelineStopped(PlayableDirector obj)
  {
     m_FirstFrameHappened = false;

     if (playableHandle != null)
     {
        var skeleton = playableHandle.Skeleton;
        skeleton.ScaleX = originalScaleX;
        skeleton.ScaleY = originalScaleY;
     }
  }

   }

}

Thanks very much as always for reporting and for providing the bugfix code already! You're the best :cooldoge:

A bugfix has been pushed to the 4.0-beta branch. The 3.8 branch remains unmodified to keep existing behaviour.
The bugfix will be released along with the next 4.0-beta unitypackage.

Issue ticket for reference:
https://github.com/EsotericSoftware/spine-runtimes/issues/1865