Last active
January 9, 2022 08:19
-
-
Save avrahamy/9ecb177eebb29033d4bda53df392730a to your computer and use it in GitHub Desktop.
A tool to scrub Spine2D animations in Unity. Select a Game Object in the scene (or in Prefab Mode) with a SkeletonAnimation, select the animation to preview and use the slider to pick the time of the frame you want to see.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEditor; | |
using UnityEngine; | |
using Spine.Unity; | |
namespace Avrahamy.Animation { | |
public class SkeletonAnimationPreviewWindow : EditorWindow { | |
private const string WINDOW_TITLE = "Skeleton Animation Preview"; | |
private string animationName; | |
private float time; | |
private SkeletonAnimation _skeleton; | |
private Spine.Animation _animation; | |
private bool animationChanged; | |
private GameObject Target { | |
get { | |
if (Selection.gameObjects.Length == 0) return null; | |
return Selection.gameObjects[0]; | |
} | |
} | |
private SkeletonAnimation Skeleton { | |
get { | |
if (_skeleton == null && Target != null) { | |
_skeleton = Target.GetComponentInParent<SkeletonAnimation>(); | |
if (_skeleton == null) { | |
_skeleton = Target.GetComponentInChildren<SkeletonAnimation>(); | |
if (_skeleton == null) { | |
_skeleton = Target.transform.root.GetComponentInChildren<SkeletonAnimation>(); | |
} | |
} | |
} | |
return _skeleton; | |
} | |
} | |
private Spine.Animation Animation { | |
get { | |
if (!string.IsNullOrEmpty(animationName) | |
&& (_animation == null || _animation.Name != animationName)) { | |
_animation = Skeleton.skeletonDataAsset.GetSkeletonData(true).FindAnimation(animationName); | |
animationChanged = true; | |
} | |
return _animation; | |
} | |
} | |
[MenuItem("Window/GG/Skeleton Preview")] | |
public static void ShowWindow() { | |
// Opens the window, otherwise focuses it if it’s already open. | |
var window = GetWindow<SkeletonAnimationPreviewWindow>(WINDOW_TITLE); | |
// Sets a minimum size to the window. | |
window.minSize = new Vector2(340f, 250f); | |
} | |
private void OnEnable() { | |
Selection.selectionChanged += OnSelectionChanged; | |
} | |
private void OnDisable() { | |
Selection.selectionChanged -= OnSelectionChanged; | |
if (_skeleton != null) { | |
ResetSkeleton(_skeleton); | |
_skeleton = null; | |
_animation = null; | |
animationName = null; | |
time = 0f; | |
} | |
} | |
private void OnSelectionChanged() { | |
var previousSkeleton = _skeleton; | |
_skeleton = Skeleton ?? previousSkeleton; | |
if (_skeleton != previousSkeleton) { | |
animationChanged = true; | |
} | |
Repaint(); | |
} | |
private void ResetSkeleton(SkeletonAnimation skeleton) { | |
var defaultAnimation = new SerializedObject(skeleton).FindProperty("_animationName").stringValue; | |
if (string.IsNullOrEmpty(defaultAnimation)) { | |
Debug.LogError("Skeleton has no default animation set. Can't reset it"); | |
return; | |
} | |
SetAnimation(defaultAnimation, 0f); | |
} | |
private void OnGUI() { | |
_skeleton = EditorGUILayout.ObjectField( | |
new GUIContent("Skeleton"), | |
Skeleton, | |
typeof(SkeletonAnimation), | |
true) as SkeletonAnimation; | |
if (Skeleton == null) return; | |
if (GUILayout.Button("Reset", GUILayout.Width(60f))) { | |
ResetSkeleton(_skeleton); | |
_skeleton = null; | |
_animation = null; | |
animationName = null; | |
time = 0f; | |
return; | |
} | |
using (new EditorGUILayout.HorizontalScope()) { | |
EditorGUILayout.LabelField("Animation", GUILayout.Width(EditorGUIUtility.labelWidth)); | |
if (EditorGUILayout.DropdownButton(new GUIContent(animationName), FocusType.Keyboard, EditorStyles.popup)) { | |
var menu = new GenericMenu(); | |
var animations = Skeleton.skeletonDataAsset.GetAnimationStateData().SkeletonData.Animations; | |
for (int i = 0; i < animations.Count; i++) { | |
var itemName = animations.Items[i].Name; | |
menu.AddItem(new GUIContent(itemName), itemName == animationName, (currentItemName) => { | |
animationName = currentItemName as string; | |
animationChanged = true; | |
}, itemName); | |
} | |
menu.ShowAsContext(); | |
} | |
} | |
if (Animation == null) return; | |
var timeChanged = false; | |
using (var check = new EditorGUI.ChangeCheckScope()) { | |
time = EditorGUILayout.Slider("Time", time, 0f, Animation.Duration); | |
if (check.changed || time > Animation.Duration) { | |
time = Mathf.Min(time, Animation.Duration); | |
timeChanged = true; | |
} | |
} | |
if (Animation != null && animationChanged) { | |
SetAnimation(animationName, time); | |
animationChanged = false; | |
} else if (timeChanged) { | |
Skeleton.AnimationState.GetCurrent(0).TrackTime = time; | |
Skeleton.LateUpdate(); | |
} | |
} | |
private void SetAnimation(string name, float time) { | |
Skeleton.AnimationState.SetAnimation( | |
0, | |
name, | |
true) | |
.TrackTime = time; | |
Skeleton.LateUpdate(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment