• Unity
  • Play an animation on editor mode

Related Discussions
...
4 أعوام لاحقا
using Spine.Unity;
using UnityEngine;
using Sirenix.OdinInspector;

[ExecuteInEditMode]
public class SpineAnimationScrubber : MonoBehaviour
{
    [ShowInInspector]
    private SkeletonAnimation skeletonAnimation;
    [ShowInInspector]
    [SpineAnimation] 
    public string animationName;
    private float previousTime = 0.0f;
    private float maxRange = 1.0f;
    [ShowInInspector, PropertyRange(0.0, 1.0f, MaxMember = "@maxRange")]
    private float currentTime = 0.0f;
    private string previousAnimationName;
    private Spine.TrackEntry anim;

private void OnValidate()
{
    if( skeletonAnimation == null )
    {
        skeletonAnimation = GetComponent<SkeletonAnimation>();
    }

    if( skeletonAnimation == null ) return;

    if( animationName != previousAnimationName )
    {
        anim = skeletonAnimation.AnimationState.SetAnimation(0, animationName, false);
        maxRange = anim.Animation.Duration * 0.5f;
        currentTime = 0.0f;
        previousTime = -1.0f;
        previousAnimationName = animationName;
    } else if( anim == null && !string.IsNullOrEmpty( animationName ) )
    {
        anim = skeletonAnimation.AnimationState.SetAnimation( 0, animationName, false );
    }

    if( anim != null && currentTime != previousTime )
    {
        if( skeletonAnimation.state == null ) return;
        anim.TrackTime = currentTime;
        skeletonAnimation.Update( currentTime );
        skeletonAnimation.state.Update( currentTime );
        skeletonAnimation.LateUpdate();
        skeletonAnimation.skeleton.Update(currentTime);
        previousTime = currentTime;
    }
}
}

I just needed something like this, so I made a simple animation scrubber. Could easily just update the current time OnInspectorGUI if you make custom inspector for it, but I had no need for that part.
I am sure the code can be optimized, but I didn't bother tbh.

Note: I am using OdinInspector for the PropertyRange attribute, but you can achieve the same dynamic range with a custom inspector.
Let me know if it helps 🙂

    Thanks for sharing your code, always much appreciated! 🙂

    6 أيام لاحقا

    Thank you!

    5 أشهر لاحقا

    Hey, does anyone know why WiseKodama had to multiply the animation duration by 0.5f here?

    I find that if i do not do this, the animation scrubs through twice as fast as it should and its on its final frame by t = trackEntry.Animation.Duration / 2.

    But i don't understand why that would be the case? Is Animation.Duration reporting a duration that's twice as long as the actual duration? What's going on there?

    • تم التحرير

    @SpiralCircus Good catch! Actually this is a bug in the source code provided by @WiseKodama, since the same delta time is applied twice to the AnimationState here:

    skeletonAnimation.Update(currentTime); // this calls animationState.Update(currentTime)
    skeletonAnimation.state.Update(currentTime); // this line should be removed

    So to correct the code, remove the second line, then you no longer need to halve the duration.
    Change this line:
    maxRange = anim.Animation.Duration * 0.5f;
    To the following:
    maxRange = anim.Animation.Duration;

      Awesome, thanks Harald!

      You're welcome, thanks for reporting.

      5 أشهر لاحقا

      Harald Thanks for the fix 🙂

      18 أيام لاحقا

      Inspired by these articles, I created a script 'EditorSkeletonPlayer' that supports SkeletonAnimation and SkeletonGraphic. I hope you find this useful.
      (If possible, I think it would be great if this feature could be officially adopted into the Spine Unity Runtime 😊)

      #if UNITY_EDITOR
      using Spine;
      using Spine.Unity;
      using UnityEditor;
      using UnityEditor.Callbacks;
      using UnityEngine;
      
      [ExecuteInEditMode]
      public class EditorSkeletonPlayer : MonoBehaviour
      {
          private IEditorSkeletonWrapper _skeletonWrapper;
          private TrackEntry _trackEntry;
          private string _oldAnimationName;
          private bool _oldLoop;
          private double _oldTime;
      
          [DidReloadScripts]
          private static void OnReloaded()
          {
              //force awake when scripts are reloaded
              var editorSpineAnimations = FindObjectsOfType<EditorSkeletonPlayer>();
      
              foreach (var editorSpineAnimation in editorSpineAnimations)
              {
                  editorSpineAnimation.Awake();
              }
          }
      
          private void Awake()
          {
              if (Application.isPlaying) return;
      
              if (_skeletonWrapper == null)
              {
                  if (TryGetComponent<SkeletonAnimation>(out var skeletonAnimation))
                  {
                      _skeletonWrapper = new SkeletonAnimationWrapper(skeletonAnimation);
                  }
                  else if (TryGetComponent<SkeletonGraphic>(out var skeletonGraphic))
                  {
                      _skeletonWrapper = new SkeletonGraphicWrapper(skeletonGraphic);
                  }
              }
      
              _oldTime = EditorApplication.timeSinceStartup;
              EditorApplication.update += EditorUpdate;
          }
      
          private void OnDestroy()
          {
              EditorApplication.update -= EditorUpdate;
          }
      
          private void EditorUpdate()
          {
              if (Application.isPlaying) return;
              if (_skeletonWrapper == null) return;
              if (_skeletonWrapper.State == null) return;
      
              if (_oldAnimationName != _skeletonWrapper.AnimationName || _oldLoop != _skeletonWrapper.Loop)
              {
                  _trackEntry = _skeletonWrapper.State.SetAnimation(0, _skeletonWrapper.AnimationName, _skeletonWrapper.Loop);
                  _oldAnimationName = _skeletonWrapper.AnimationName;
                  _oldLoop = _skeletonWrapper.Loop;
              }
      
              if (_trackEntry != null)
              {
                  _trackEntry.TimeScale = _skeletonWrapper.Speed;
              }
      
              float deltaTime = (float)(EditorApplication.timeSinceStartup - _oldTime);
              _skeletonWrapper.Update(deltaTime);
              _oldTime = EditorApplication.timeSinceStartup;
      
              //force repaint to update animation
              EditorApplication.QueuePlayerLoopUpdate();
          }
      
          private class SkeletonAnimationWrapper : IEditorSkeletonWrapper
          {
              private SkeletonAnimation _skeletonAnimation;
      
              public SkeletonAnimationWrapper(SkeletonAnimation skeletonAnimation)
              {
                  _skeletonAnimation = skeletonAnimation;
              }
      
              public string AnimationName
              {
                  get { return _skeletonAnimation.AnimationName; }
              }
      
              public bool Loop
              {
                  get { return _skeletonAnimation.loop; }
              }
      
              public float Speed
              {
                  get { return _skeletonAnimation.timeScale; }
              }
      
              public Spine.AnimationState State
              {
                  get { return _skeletonAnimation.state; }
              }
      
              public void Update(float deltaTime)
              {
                  _skeletonAnimation.Update(deltaTime);
      
              }
          }
      
          private class SkeletonGraphicWrapper : IEditorSkeletonWrapper
          {
              private SkeletonGraphic _skeletonGraphic;
      
              public SkeletonGraphicWrapper(SkeletonGraphic skeletonGraphic)
              {
                  _skeletonGraphic = skeletonGraphic;
              }
      
              public string AnimationName
              {
                  get { return _skeletonGraphic.startingAnimation; }
              }
      
              public bool Loop
              {
                  get { return _skeletonGraphic.startingLoop; }
              }
      
              public float Speed
              {
                  get { return _skeletonGraphic.timeScale; }
              }
      
              public Spine.AnimationState State
              {
                  get { return _skeletonGraphic.AnimationState; }
              }
      
              public void Update(float deltaTime)
              {
                  _skeletonGraphic.Update(deltaTime);
              }
          }
      
          private interface IEditorSkeletonWrapper
          {
              string AnimationName { get; }
      
              bool Loop { get; }
      
              float Speed { get; }
      
              Spine.AnimationState State { get; }
      
              void Update(float deltaTime);
          }
      }
      #endif

        @Sekomu Awesome, thanks very much for sharing!

        Sekomu (If possible, I think it would be great if this feature could be officially adopted into the Spine Unity Runtime 😊)

        We would be happy to include such a component in the spine-runtimes officially (potentially with some slight modifications)! Could you please send us a signed contributor license agreement (CLA) to contact@esotericsoftware.com as listed in the Contributing section here:
        https://github.com/EsotericSoftware/spine-runtimes/tree/4.1/#contributing

          No need to resend the CLA! @Harald will have a look at the pull request.

          @Sekomu Thanks very much for the pull request, sorry that I missed that you already sent a CLA in the past!

          We have merged the pull request and added some improvements on top of it, e.g. added single frame preview as well via a Fixed Track Time parameter. This has been requested in this issue ticket.

          From the changelog:

          Added experimental EditorSkeletonPlayer component to allow Editor playback of the initial animation set at SkeletonAnimation or SkeletonGraphic components. Add this component to your skeleton GameObject to enable the in-editor animation preview. Allows configurations for continuous playback when selected, deselected, and alternative single-frame preview by setting Fixed Track Time to any value other than 0. Limitations: At skeletons with variable material count the Inspector preview may be too unresponsive. It is then recommended to disable the EditorSkeletonPlayer component (at the top of the Inspector) to make it responsive again, then you can disable Play When Selected and re-enable the component to preview playback only when deselected.

          New spine-unity 4.1 and 4.2-beta unitypackages are available here as usual:
          https://esotericsoftware.com/spine-unity-download