diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index 583a300fe..ce30bd8d3 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -37,11 +37,15 @@ namespace Spine { public class Animation { internal String name; internal ExposedList timelines; + internal HashSet timelineIds; internal float duration; public Animation (string name, ExposedList timelines, float duration) { if (name == null) throw new ArgumentNullException("name", "name cannot be null."); if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.timelineIds = new HashSet(); + foreach (Timeline timeline in timelines) + timelineIds.Add(timeline.PropertyId); this.name = name; this.timelines = timelines; this.duration = duration; @@ -55,6 +59,11 @@ namespace Spine { /// The animation's name, which is unique across all animations in the skeleton. public string Name { get { return name; } } + /// Whether the timeline with the property id is contained in this animation. + public bool HasTimeline (int id) { + return timelineIds.Contains(id); + } + /// Applies all the animation's timelines to the specified skeleton. /// public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixBlend blend, diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index b0359ecf8..3dfed92ae 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -777,11 +777,11 @@ namespace Spine { if (!propertyIDs.Add(id)) timelineMode[i] = AnimationState.Subsequent; else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline - || timeline is EventTimeline || !HasTimeline(to, id)) { + || timeline is EventTimeline || !to.animation.HasTimeline(id)) { timelineMode[i] = AnimationState.First; } else { for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) { - if (HasTimeline(next, id)) continue; + if (next.animation.HasTimeline(id)) continue; if (next.mixDuration > 0) { timelineMode[i] = AnimationState.HoldMix; timelineHoldMix[i] = next; @@ -809,13 +809,6 @@ namespace Spine { } } - static bool HasTimeline (TrackEntry entry, int id) { - var timelines = entry.animation.timelines.Items; - for (int i = 0, n = entry.animation.timelines.Count; i < n; i++) - if (timelines[i].PropertyId == id) return true; - return false; - } - /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. public TrackEntry GetCurrent (int trackIndex) { if (trackIndex >= tracks.Count) return null; diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs index ba20dfa98..16d6257ba 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs @@ -138,6 +138,9 @@ namespace Spine.Unity.Editor { const string MECANIM_EVENT_INCLUDE_FOLDERNAME_KEY = "SPINE_MECANIM_EVENT_INCLUDE_FOLDERNAME"; public static bool mecanimEventIncludeFolderName = SpinePreferences.DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME; + const string TIMELINE_USE_BLEND_DURATION_KEY = "SPINE_TIMELINE_USE_BLEND_DURATION_KEY"; + public static bool timelineUseBlendDuration = SpinePreferences.DEFAULT_TIMELINE_USE_BLEND_DURATION; + static bool preferencesLoaded = false; public static void Load () { @@ -154,6 +157,7 @@ namespace Spine.Unity.Editor { mecanimEventIncludeFolderName = EditorPrefs.GetBool(MECANIM_EVENT_INCLUDE_FOLDERNAME_KEY, SpinePreferences.DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME); atlasTxtImportWarning = EditorPrefs.GetBool(ATLASTXT_WARNING_KEY, SpinePreferences.DEFAULT_ATLASTXT_WARNING); textureImporterWarning = EditorPrefs.GetBool(TEXTUREIMPORTER_WARNING_KEY, SpinePreferences.DEFAULT_TEXTUREIMPORTER_WARNING); + timelineUseBlendDuration = EditorPrefs.GetBool(TIMELINE_USE_BLEND_DURATION_KEY, SpinePreferences.DEFAULT_TIMELINE_USE_BLEND_DURATION); SpineHandles.handleScale = EditorPrefs.GetFloat(SCENE_ICONS_SCALE_KEY, DEFAULT_SCENE_ICONS_SCALE); preferencesLoaded = true; @@ -171,6 +175,7 @@ namespace Spine.Unity.Editor { newPreferences.mecanimEventIncludeFolderName = EditorPrefs.GetBool(MECANIM_EVENT_INCLUDE_FOLDERNAME_KEY, SpinePreferences.DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME); newPreferences.atlasTxtImportWarning = EditorPrefs.GetBool(ATLASTXT_WARNING_KEY, SpinePreferences.DEFAULT_ATLASTXT_WARNING); newPreferences.textureImporterWarning = EditorPrefs.GetBool(TEXTUREIMPORTER_WARNING_KEY, SpinePreferences.DEFAULT_TEXTUREIMPORTER_WARNING); + newPreferences.timelineUseBlendDuration = EditorPrefs.GetBool(TIMELINE_USE_BLEND_DURATION_KEY, SpinePreferences.DEFAULT_TIMELINE_USE_BLEND_DURATION); } public static void SaveToEditorPrefs(SpinePreferences preferences) { @@ -184,6 +189,7 @@ namespace Spine.Unity.Editor { EditorPrefs.SetBool(MECANIM_EVENT_INCLUDE_FOLDERNAME_KEY, preferences.mecanimEventIncludeFolderName); EditorPrefs.SetBool(ATLASTXT_WARNING_KEY, preferences.atlasTxtImportWarning); EditorPrefs.SetBool(TEXTUREIMPORTER_WARNING_KEY, preferences.textureImporterWarning); + EditorPrefs.SetBool(TIMELINE_USE_BLEND_DURATION_KEY, preferences.timelineUseBlendDuration); } #endif @@ -265,6 +271,12 @@ namespace Spine.Unity.Editor { if (GUILayout.Button("Disable", GUILayout.Width(64))) SpineTK2DEditorUtility.DisableTK2D(); } + + GUILayout.Space(20); + EditorGUILayout.LabelField("Timeline Extension", EditorStyles.boldLabel); + { + SpineEditorUtilities.BoolPrefsField(ref timelineUseBlendDuration, TIMELINE_USE_BLEND_DURATION_KEY, new GUIContent("Use Blend Duration", "When enabled, MixDuration will be synced with timeline clip transition duration 'Ease In Duration'.")); + } } #endif // !NEW_PREFERENCES_SETTINGS_PROVIDER } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs index f41ac733a..094e75e4b 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs @@ -184,9 +184,7 @@ namespace Spine.Unity.Editor { Debug.LogError("Could not export Spine Skeleton because SkeletonDataAsset is null or invalid!"); return; } - - #if !NEW_PREFAB_SYSTEM - + if (outputPath == "") { outputPath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(skeletonDataAsset)) + "/Baked"; System.IO.Directory.CreateDirectory(outputPath); @@ -281,7 +279,13 @@ namespace Spine.Unity.Editor { Object prefab = AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject)); if (prefab == null) { + #if NEW_PREFAB_SYSTEM + GameObject emptyGameObject = new GameObject(); + prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(emptyGameObject, prefabPath, InteractionMode.AutomatedAction); + GameObject.DestroyImmediate(emptyGameObject); + #else prefab = PrefabUtility.CreateEmptyPrefab(prefabPath); + #endif newPrefab = true; } @@ -428,14 +432,22 @@ namespace Spine.Unity.Editor { } if (newPrefab) { + #if NEW_PREFAB_SYSTEM + PrefabUtility.SaveAsPrefabAssetAndConnect(prefabRoot, prefabPath, InteractionMode.AutomatedAction); + #else PrefabUtility.ReplacePrefab(prefabRoot, prefab, ReplacePrefabOptions.ConnectToPrefab); + #endif } else { foreach (string str in unusedMeshNames) { Mesh.DestroyImmediate(meshTable[str], true); } + #if NEW_PREFAB_SYSTEM + PrefabUtility.SaveAsPrefabAssetAndConnect(prefabRoot, prefabPath, InteractionMode.AutomatedAction); + #else PrefabUtility.ReplacePrefab(prefabRoot, prefab, ReplacePrefabOptions.ReplaceNameBased); + #endif } @@ -447,8 +459,6 @@ namespace Spine.Unity.Editor { GameObject.DestroyImmediate(prefabRoot); } - #endif - } #region Attachment Baking diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBakingWindow.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBakingWindow.cs index 42de92cf6..08cb58713 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBakingWindow.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBakingWindow.cs @@ -27,10 +27,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER -#define NEW_PREFAB_SYSTEM -#endif - using System.Collections; using System.Collections.Generic; using UnityEngine; @@ -44,7 +40,6 @@ namespace Spine.Unity.Editor { public class SkeletonBakingWindow : EditorWindow { const bool IsUtilityWindow = true; - #if !NEW_PREFAB_SYSTEM [MenuItem("CONTEXT/SkeletonDataAsset/Skeleton Baking", false, 5000)] public static void Init (MenuCommand command) { var window = EditorWindow.GetWindow(IsUtilityWindow); @@ -54,7 +49,6 @@ namespace Spine.Unity.Editor { window.skeletonDataAsset = command.context as SkeletonDataAsset; window.Show(); } - #endif public SkeletonDataAsset skeletonDataAsset; [SpineSkin(dataField:"skeletonDataAsset")] diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs index 6311fb559..4343311b9 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs @@ -92,6 +92,10 @@ namespace Spine.Unity.Editor { public const bool DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME = true; public bool mecanimEventIncludeFolderName = DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME; + // Timeline extension module + public const bool DEFAULT_TIMELINE_USE_BLEND_DURATION = true; + public bool timelineUseBlendDuration = DEFAULT_TIMELINE_USE_BLEND_DURATION; + #if NEW_PREFERENCES_SETTINGS_PROVIDER public static void Load () { SpineHandles.handleScale = EditorPrefs.GetFloat(SCENE_ICONS_SCALE_KEY, DEFAULT_SCENE_ICONS_SCALE); @@ -185,6 +189,12 @@ namespace Spine.Unity.Editor { if (GUILayout.Button("Disable", GUILayout.Width(64))) SpineEditorUtilities.SpineTK2DEditorUtility.DisableTK2D(); } + + GUILayout.Space(20); + EditorGUILayout.LabelField("Timeline Extension", EditorStyles.boldLabel); + { + EditorGUILayout.PropertyField(settings.FindProperty("timelineUseBlendDuration"), new GUIContent("Use Blend Duration", "When enabled, MixDuration will be synced with timeline clip transition duration 'Ease In Duration'.")); + } } EditorGUIUtility.labelWidth = prevLabelWidth; } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs index 73d981778..955e57d74 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs @@ -96,8 +96,11 @@ namespace Spine.Unity { } } set { - if (_animationName == value) - return; + if (_animationName == value) { + TrackEntry entry = state.GetCurrent(0); + if (entry != null && entry.loop == loop) + return; + } _animationName = value; if (string.IsNullOrEmpty(value)) { diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs index dde7a7a5e..a6e05b8f5 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -288,6 +288,8 @@ namespace Spine.Unity { if (!Application.isPlaying) Update(0f); #endif + if (freeze) + Update(0f); } } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs index 02c882a3d..0a241356b 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs @@ -580,7 +580,9 @@ namespace Spine.Unity.AttachmentTools { static void CopyTextureAttributesFrom(this Texture2D destination, Texture2D source) { destination.filterMode = source.filterMode; destination.anisoLevel = source.anisoLevel; + #if UNITY_EDITOR destination.alphaIsTransparency = source.alphaIsTransparency; + #endif destination.wrapModeU = source.wrapModeU; destination.wrapModeV = source.wrapModeV; destination.wrapModeW = source.wrapModeW; diff --git a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateClipInspector.cs b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateClipInspector.cs new file mode 100644 index 000000000..3146633c7 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateClipInspector.cs @@ -0,0 +1,116 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated May 1, 2019. Replaces all prior versions. + * + * Copyright (c) 2013-2019, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS + * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using UnityEditor; +using Spine.Unity.Playables; +using UnityEngine.Timeline; + +namespace Spine.Unity.Editor { + + [CustomEditor(typeof(SpineAnimationStateClip))] + [CanEditMultipleObjects] + public class SpineAnimationStateClipInspector : UnityEditor.Editor { + + protected SerializedProperty templateProp = null; + + protected class ClipInfo { + public TimelineClip timelineClip; + public float previousBlendInDuration = -1.0f; + public float unblendedMixDuration = 0.2f; + } + + protected ClipInfo[] clipInfo = null; + + public void OnEnable () { + templateProp = serializedObject.FindProperty("template"); + System.Array.Resize(ref clipInfo, targets.Length); + for (int i = 0; i < targets.Length; ++i) { + var clip = (SpineAnimationStateClip)targets[i]; + clipInfo[i] = new ClipInfo(); + clipInfo[i].timelineClip = FindTimelineClip(clip); + } + } + + public override void OnInspectorGUI () { + serializedObject.Update(); + EditorGUILayout.PropertyField(templateProp); + + for (int i = 0; i < targets.Length; ++i) { + var targetClip = (SpineAnimationStateClip)targets[i]; + if (targetClip.template.useBlendDuration) + AdjustMixDuration(targetClip, clipInfo[i]); + } + + serializedObject.ApplyModifiedProperties(); + } + + protected void AdjustMixDuration(SpineAnimationStateClip targetClip, ClipInfo timelineClipInfo) { + + if (timelineClipInfo == null) + return; + + var timelineClip = timelineClipInfo.timelineClip; + if (timelineClip == null) + return; + + float blendInDur = (float)timelineClip.blendInDuration; + bool isBlendingNow = blendInDur > 0; + bool wasBlendingBefore = timelineClipInfo.previousBlendInDuration > 0; + + if (isBlendingNow) { + if (!wasBlendingBefore) { + timelineClipInfo.unblendedMixDuration = targetClip.template.mixDuration; + } + targetClip.template.mixDuration = blendInDur; + EditorUtility.SetDirty(targetClip); + } + else if (wasBlendingBefore) { + targetClip.template.mixDuration = timelineClipInfo.unblendedMixDuration; + EditorUtility.SetDirty(targetClip); + } + timelineClipInfo.previousBlendInDuration = blendInDur; + } + + protected TimelineClip FindTimelineClip(SpineAnimationStateClip targetClip) { + string[] guids = AssetDatabase.FindAssets("t:TimelineAsset"); + foreach (string guid in guids) { + TimelineAsset timeline = (TimelineAsset)AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid), typeof(TimelineAsset)); + foreach (var track in timeline.GetOutputTracks()) { + foreach (var clip in track.GetClips()) { + if (clip.asset.GetType() == typeof(SpineAnimationStateClip) && object.ReferenceEquals(clip.asset, targetClip)) { + return clip; + } + } + } + } + return null; + } + + } +} diff --git a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateClipInspector.cs.meta b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateClipInspector.cs.meta new file mode 100644 index 000000000..2bf0bf181 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateClipInspector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 95642d062fbcfda4e8d9262fa715b098 +timeCreated: 1570044805 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateDrawer.cs b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateDrawer.cs index 7b8ce4453..dcd472ea7 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateDrawer.cs +++ b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineAnimationStateDrawer.cs @@ -32,30 +32,39 @@ using UnityEngine; using Spine; using Spine.Unity; using Spine.Unity.Playables; +using Spine.Unity.Editor; -//[CustomPropertyDrawer(typeof(SpineAnimationStateBehaviour))] +[CustomPropertyDrawer(typeof(SpineAnimationStateBehaviour))] public class SpineAnimationStateDrawer : PropertyDrawer { - /* + public override float GetPropertyHeight (SerializedProperty property, GUIContent label) { const int fieldCount = 8; return fieldCount * EditorGUIUtility.singleLineHeight; } public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) { - SerializedProperty skeletonDataAssetProp = property.FindPropertyRelative("skeletonDataAsset"); - SerializedProperty animationNameProp = property.FindPropertyRelative("animationName"); + SerializedProperty animationReferenceProp = property.FindPropertyRelative("animationReference"); SerializedProperty loopProp = property.FindPropertyRelative("loop"); + + SerializedProperty customDurationProp = property.FindPropertyRelative("customDuration"); + SerializedProperty useBlendDurationProp = property.FindPropertyRelative("useBlendDuration"); + SerializedProperty mixDurationProp = property.FindPropertyRelative("mixDuration"); SerializedProperty eventProp = property.FindPropertyRelative("eventThreshold"); SerializedProperty attachmentProp = property.FindPropertyRelative("attachmentThreshold"); SerializedProperty drawOrderProp = property.FindPropertyRelative("drawOrderThreshold"); + // initialize useBlendDuration parameter according to preferences + SerializedProperty isInitializedProp = property.FindPropertyRelative("isInitialized"); + if (!isInitializedProp.hasMultipleDifferentValues && isInitializedProp.boolValue == false) { + useBlendDurationProp.boolValue = SpineEditorUtilities.Preferences.timelineUseBlendDuration; + isInitializedProp.boolValue = true; + } + Rect singleFieldRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); - EditorGUI.PropertyField(singleFieldRect, skeletonDataAssetProp); float lineHeightWithSpacing = EditorGUIUtility.singleLineHeight + 2f; - singleFieldRect.y += lineHeightWithSpacing; - EditorGUI.PropertyField(singleFieldRect, animationNameProp); + EditorGUI.PropertyField(singleFieldRect, animationReferenceProp); singleFieldRect.y += lineHeightWithSpacing; EditorGUI.PropertyField(singleFieldRect, loopProp); @@ -65,6 +74,19 @@ public class SpineAnimationStateDrawer : PropertyDrawer { singleFieldRect.y += lineHeightWithSpacing; EditorGUI.LabelField(singleFieldRect, "Mixing Settings", EditorStyles.boldLabel); + singleFieldRect.y += lineHeightWithSpacing; + EditorGUI.PropertyField(singleFieldRect, customDurationProp); + + bool greyOutCustomDurations = (!customDurationProp.hasMultipleDifferentValues && + customDurationProp.boolValue == false); + using (new EditorGUI.DisabledGroupScope(greyOutCustomDurations)) { + singleFieldRect.y += lineHeightWithSpacing; + EditorGUI.PropertyField(singleFieldRect, useBlendDurationProp); + + singleFieldRect.y += lineHeightWithSpacing; + EditorGUI.PropertyField(singleFieldRect, mixDurationProp); + } + singleFieldRect.y += lineHeightWithSpacing; EditorGUI.PropertyField(singleFieldRect, eventProp); @@ -74,5 +96,4 @@ public class SpineAnimationStateDrawer : PropertyDrawer { singleFieldRect.y += lineHeightWithSpacing; EditorGUI.PropertyField(singleFieldRect, drawOrderProp); } - */ } diff --git a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineSkeletonFlipClipInspector.cs b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineSkeletonFlipClipInspector.cs new file mode 100644 index 000000000..d309d2af5 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineSkeletonFlipClipInspector.cs @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated May 1, 2019. Replaces all prior versions. + * + * Copyright (c) 2013-2019, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS + * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using UnityEditor; +using Spine.Unity.Playables; + +namespace Spine.Unity.Editor { + + [CustomEditor(typeof(SpineSkeletonFlipClip))] + [CanEditMultipleObjects] + public class SpineSkeletonFlipClipInspector : UnityEditor.Editor { + + protected SerializedProperty templateProp = null; + + public void OnEnable () { + templateProp = serializedObject.FindProperty("template"); + } + + public override void OnInspectorGUI () { + serializedObject.Update(); + EditorGUILayout.PropertyField(templateProp); + serializedObject.ApplyModifiedProperties(); + } + } +} diff --git a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineSkeletonFlipClipInspector.cs.meta b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineSkeletonFlipClipInspector.cs.meta new file mode 100644 index 000000000..8d76bb6f9 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Editor/SpineSkeletonFlipClipInspector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c6d7cdfbf1ccc0042b92586542f085a4 +timeCreated: 1570045635 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateBehaviour.cs b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateBehaviour.cs index f9827e47f..1a1d028de 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateBehaviour.cs +++ b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateBehaviour.cs @@ -44,8 +44,11 @@ namespace Spine.Unity.Playables { public AnimationReferenceAsset animationReference; public bool loop; - [Header("Mix Properties")] + // Mix Properties public bool customDuration = false; + public bool useBlendDuration = true; + [SerializeField] + private bool isInitialized = false; // required to read preferences values from editor side. public float mixDuration = 0.1f; [Range(0, 1f)] diff --git a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateClip.cs b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateClip.cs index ed62715e1..21a0a9034 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateClip.cs +++ b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateClip.cs @@ -37,13 +37,21 @@ namespace Spine.Unity.Playables { public class SpineAnimationStateClip : PlayableAsset, ITimelineClipAsset { public SpineAnimationStateBehaviour template = new SpineAnimationStateBehaviour(); - public ClipCaps clipCaps { get { return ClipCaps.None; } } + public ClipCaps clipCaps { get { return ClipCaps.Blending | ClipCaps.ClipIn | ClipCaps.SpeedMultiplier | (template.loop ? ClipCaps.Looping : 0); } } public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) { var playable = ScriptPlayable.Create(graph, template); playable.GetBehaviour(); return playable; } + + public override double duration { + get { + if (template.animationReference == null) + return 0; + return template.animationReference.Animation.Duration; + } + } } } diff --git a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs index b92da4a52..3d03d275c 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs +++ b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs @@ -70,7 +70,7 @@ namespace Spine.Unity.Playables { for (int i = 0; i < inputCount; i++) { float lastInputWeight = lastInputWeights[i]; float inputWeight = playable.GetInputWeight(i); - bool trackStarted = inputWeight > lastInputWeight; + bool trackStarted = lastInputWeight == 0 && inputWeight > 0; lastInputWeights[i] = inputWeight; if (trackStarted) { @@ -84,9 +84,10 @@ namespace Spine.Unity.Playables { if (clipData.animationReference.Animation != null) { Spine.TrackEntry trackEntry = state.SetAnimation(trackIndex, clipData.animationReference.Animation, clipData.loop); - //trackEntry.TrackTime = (float)inputPlayable.GetTime(); // More accurate time-start? trackEntry.EventThreshold = clipData.eventThreshold; trackEntry.DrawOrderThreshold = clipData.drawOrderThreshold; + trackEntry.TrackTime = (float)inputPlayable.GetTime() * (float)inputPlayable.GetSpeed(); + trackEntry.TimeScale = (float)inputPlayable.GetSpeed(); trackEntry.AttachmentThreshold = clipData.attachmentThreshold; if (clipData.customDuration) @@ -111,15 +112,15 @@ namespace Spine.Unity.Playables { if (spineComponent == null) return; int inputCount = playable.GetInputCount(); - int lastOneWeight = -1; + int lastNonZeroWeightTrack = -1; for (int i = 0; i < inputCount; i++) { float inputWeight = playable.GetInputWeight(i); - if (inputWeight >= 1) lastOneWeight = i; + if (inputWeight > 0) lastNonZeroWeightTrack = i; } - if (lastOneWeight != -1) { - ScriptPlayable inputPlayableClip = (ScriptPlayable)playable.GetInput(lastOneWeight); + if (lastNonZeroWeightTrack != -1) { + ScriptPlayable inputPlayableClip = (ScriptPlayable)playable.GetInput(lastNonZeroWeightTrack); SpineAnimationStateBehaviour clipData = inputPlayableClip.GetBehaviour(); var skeleton = spineComponent.Skeleton; @@ -133,16 +134,16 @@ namespace Spine.Unity.Playables { Animation fromAnimation = null; float fromClipTime = 0; bool fromClipLoop = false; - if (lastOneWeight != 0 && inputCount > 1) { - var fromClip = (ScriptPlayable)playable.GetInput(lastOneWeight - 1); + if (lastNonZeroWeightTrack != 0 && inputCount > 1) { + var fromClip = (ScriptPlayable)playable.GetInput(lastNonZeroWeightTrack - 1); var fromClipData = fromClip.GetBehaviour(); fromAnimation = fromClipData.animationReference != null ? fromClipData.animationReference.Animation : null; - fromClipTime = (float)fromClip.GetTime(); + fromClipTime = (float)fromClip.GetTime() * (float)fromClip.GetSpeed(); fromClipLoop = fromClipData.loop; } Animation toAnimation = clipData.animationReference != null ? clipData.animationReference.Animation : null; - float toClipTime = (float)inputPlayableClip.GetTime(); + float toClipTime = (float)inputPlayableClip.GetTime() * (float)inputPlayableClip.GetSpeed(); float mixDuration = clipData.mixDuration; if (!clipData.customDuration && fromAnimation != null && toAnimation != null) {