diff --git a/CHANGELOG.md b/CHANGELOG.md index 38de66eac..ab39df551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -360,6 +360,8 @@ * Added support for **multiple atlas textures at `SkeletonGraphic`**. You can enable this feature by enabling the parameter `Multiple CanvasRenders` in the `Advanced` section of the `SkeletonGraphic` Inspector. This automatically creates the required number of child `CanvasRenderer` GameObjects for each required draw call (submesh). * Added support for **Render Separator Slots** at `SkeletonGraphic`. Render separation can be enabled directly in the `Advanced` section of the `SkeletonGraphic` Inspector, it does not require any additional components (like `SkeletonRenderSeparator` or `SkeletonPartsRenderer` for `SkeletonRenderer` components). When enabled, additional separator GameObjects will be created automatically for each separation part, and `CanvasRenderer` GameObjects re-parented to them accordingly. The separator GameObjects can be moved around and re-parented in the hierarchy according to your requirements to achieve the desired draw order within your `Canvas`. A usage example can be found in the updated `Spine Examples/Other Examples/SkeletonRenderSeparator` scene. * Added `SkeletonGraphicCustomMaterials` component, providing functionality to override materials and textures of a `SkeletonGraphic`, similar to `SkeletonRendererCustomMaterials`. Note: overriding materials or textures per slot is not provided due to structural limitations. + * Added **Root Motion support** for `SkeletonAnimation`, `SkeletonMecanim` and `SkeletonGraphic` via new components `SkeletonRootMotion` and `SkeletonMecanimRootMotion`. The `SkeletonAnimation` and `SkeletonGraphic` component Inspector now provides a line `Root Motion` with `Add Component` and `Remove Component` buttons to add/remove the new `SkeletonRootMotion` component to your GameObject. The `SkeletonMecanim` Inspector detects whether root motion is enabled at the `Animator` component and adds a `SkeletonMecanimRootMotion` component automatically. + * `SkeletonMecanim` now provides an additional `Custom MixMode` parameter under `Mecanim Translator`. It is enabled by default in version 3.8 to maintain current behaviour, using the set `Mix Mode` for each Mecanim layer. When disabled, `SkeletonMecanim` will use the recommended `MixMode` according to the layer blend mode. Additional information can be found in the [Mecanim Translator section](http://esotericsoftware.com/spine-unity#Parameters-for-animation-blending-control) on the spine-unity documentation pages. * **Changes of default values** * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`. diff --git a/spine-cocos2dx/src/spine/SkeletonRenderer.cpp b/spine-cocos2dx/src/spine/SkeletonRenderer.cpp index 569c69d1a..d7331158f 100644 --- a/spine-cocos2dx/src/spine/SkeletonRenderer.cpp +++ b/spine-cocos2dx/src/spine/SkeletonRenderer.cpp @@ -48,6 +48,7 @@ namespace spine { bool cullRectangle(Renderer* renderer, const Mat4& transform, const cocos2d::Rect& rect); Color4B ColorToColor4B(const Color& color); bool slotIsOutRange(Slot& slot, int startSlotIndex, int endSlotIndex); + bool nothingToDraw(Slot& slot, int startSlotIndex, int endSlotIndex); } // C Variable length array @@ -301,18 +302,7 @@ namespace spine { for (int i = 0, n = _skeleton->getSlots().size(); i < n; ++i) { Slot* slot = _skeleton->getDrawOrder()[i];; - if (slotIsOutRange(*slot, _startSlotIndex, _endSlotIndex)) { - _clipper->clipEnd(*slot); - continue; - } - - if (!slot->getAttachment()) { - _clipper->clipEnd(*slot); - continue; - } - - // Early exit if slot is invisible - if (slot->getColor().a == 0 || !slot->getBone().isActive()) { + if (nothingToDraw(*slot, _startSlotIndex, _endSlotIndex)) { _clipper->clipEnd(*slot); continue; } @@ -324,12 +314,6 @@ namespace spine { RegionAttachment* attachment = static_cast(slot->getAttachment()); attachmentVertices = static_cast(attachment->getRendererObject()); - // Early exit if attachment is invisible - if (attachment->getColor().a == 0) { - _clipper->clipEnd(*slot); - continue; - } - float* dstTriangleVertices = nullptr; int dstStride = 0; // in floats if (hasSingleTint) { @@ -556,7 +540,7 @@ namespace spine { } } } else { - + #if COCOS2D_VERSION < 0x00040000 TwoColorTrianglesCommand* batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, trianglesTwoColor, transform, transformFlags); #else @@ -641,7 +625,7 @@ namespace spine { #endif DrawNode* drawNode = DrawNode::create(); - drawNode->setGlobalZOrder(getGlobalZOrder()); + drawNode->setGlobalZOrder(getGlobalZOrder()); // Draw bounding rectangle if (_debugBoundingRect) { @@ -935,21 +919,32 @@ namespace spine { return startSlotIndex > index || endSlotIndex < index; } + bool nothingToDraw(Slot& slot, int startSlotIndex, int endSlotIndex) { + Attachment *attachment = slot.getAttachment(); + if (!attachment || + slotIsOutRange(slot, startSlotIndex, endSlotIndex) || + !slot.getBone().isActive() || + slot.getColor().a == 0) + return true; + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + if (static_cast(attachment)->getColor().a == 0) + return true; + } + else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + if (static_cast(attachment)->getColor().a == 0) + return true; + } + return false; + } + int computeTotalCoordCount(Skeleton& skeleton, int startSlotIndex, int endSlotIndex) { int coordCount = 0; for (size_t i = 0; i < skeleton.getSlots().size(); ++i) { Slot& slot = *skeleton.getSlots()[i]; + if (nothingToDraw(slot, startSlotIndex, endSlotIndex)) { + continue; + } Attachment* const attachment = slot.getAttachment(); - if (!attachment) { - continue; - } - if (slotIsOutRange(slot, startSlotIndex, endSlotIndex)) { - continue; - } - // Early exit if slot is invisible - if (slot.getColor().a == 0) { - continue; - } if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { coordCount += 8; } @@ -969,16 +964,10 @@ namespace spine { #endif for (size_t i = 0; i < skeleton.getSlots().size(); ++i) { /*const*/ Slot& slot = *skeleton.getDrawOrder()[i]; // match the draw order of SkeletonRenderer::Draw + if (nothingToDraw(slot, startSlotIndex, endSlotIndex)) { + continue; + } Attachment* const attachment = slot.getAttachment(); - if (!attachment) { - continue; - } - if (slotIsOutRange(slot, startSlotIndex, endSlotIndex)) { - continue; - } - if (slot.getColor().a == 0) { - continue; - } if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { RegionAttachment* const regionAttachment = static_cast(attachment); assert(dstPtr + 8 <= dstEnd); @@ -1010,7 +999,7 @@ namespace spine { BlendFunc makeBlendFunc(BlendMode blendMode, bool premultipliedAlpha) { BlendFunc blendFunc; - + #if COCOS2D_VERSION < 0x00040000 switch (blendMode) { case BlendMode_Additive: @@ -1056,15 +1045,15 @@ namespace spine { bool cullRectangle(Renderer* renderer, const Mat4& transform, const cocos2d::Rect& rect) { if (Camera::getVisitingCamera() == nullptr) return false; - + auto director = Director::getInstance(); auto scene = director->getRunningScene(); - + if (!scene || (scene && Camera::getDefaultCamera() != Camera::getVisitingCamera())) return false; Rect visibleRect(director->getVisibleOrigin(), director->getVisibleSize()); - + // transform center point to screen space float hSizeX = rect.size.width/2; float hSizeY = rect.size.height/2; @@ -1075,7 +1064,7 @@ namespace spine { // convert content size to world coordinates float wshw = std::max(fabsf(hSizeX * transform.m[0] + hSizeY * transform.m[4]), fabsf(hSizeX * transform.m[0] - hSizeY * transform.m[4])); float wshh = std::max(fabsf(hSizeX * transform.m[1] + hSizeY * transform.m[5]), fabsf(hSizeX * transform.m[1] - hSizeY * transform.m[5])); - + // enlarge visible rect half size in screen coord visibleRect.origin.x -= wshw; visibleRect.origin.y -= wshh; diff --git a/spine-cpp/spine-cpp/src/spine/AnimationState.cpp b/spine-cpp/spine-cpp/src/spine/AnimationState.cpp index 657770396..845c5e206 100644 --- a/spine-cpp/spine-cpp/src/spine/AnimationState.cpp +++ b/spine-cpp/spine-cpp/src/spine/AnimationState.cpp @@ -1031,9 +1031,9 @@ void AnimationState::computeHold(TrackEntry *entry) { } else { for (TrackEntry *next = to->_mixingTo; next != NULL; next = next->_mixingTo) { if (next->_animation->hasTimeline(id)) continue; - if (entry->_mixDuration > 0) { + if (next->_mixDuration > 0) { timelineMode[i] = HoldMix; - timelineHoldMix[i] = entry; + timelineHoldMix[i] = next; i++; goto continue_outer; // continue outer; } diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index e0107fb84..70c1b33ee 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -43,9 +43,13 @@ namespace Spine { 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); + // Note: avoiding reallocations by adding all hash set entries at + // once (EnsureCapacity() is only available in newer .Net versions). + int[] propertyIDs = new int[timelines.Count]; + for (int i = 0; i < timelines.Count; ++i) { + propertyIDs[i] = timelines.Items[i].PropertyId; + } + this.timelineIds = new HashSet(propertyIDs); this.name = name; this.timelines = timelines; this.duration = duration; diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index 67c66a8d1..808373047 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -85,6 +85,25 @@ namespace Spine { public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); public event TrackEntryEventDelegate Event; + + public void AssignEventSubscribersFrom (AnimationState src) { + Event = src.Event; + Start = src.Start; + Interrupt = src.Interrupt; + End = src.End; + Dispose = src.Dispose; + Complete = src.Complete; + } + + public void AddEventSubscribersFrom (AnimationState src) { + Event += src.Event; + Start += src.Start; + Interrupt += src.Interrupt; + End += src.End; + Dispose += src.Dispose; + Complete += src.Complete; + } + // end of difference private readonly EventQueue queue; // Initialized by constructor. private readonly HashSet propertyIDs = new HashSet(); diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs index e4ce5538c..810c22b72 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -883,6 +883,7 @@ namespace Spine { internal class SkeletonInput { private byte[] chars = new byte[32]; + private byte[] bytesBigEndian = new byte[4]; internal ExposedList strings; Stream input; @@ -905,15 +906,20 @@ namespace Spine { } public float ReadFloat () { - chars[3] = (byte)input.ReadByte(); - chars[2] = (byte)input.ReadByte(); - chars[1] = (byte)input.ReadByte(); - chars[0] = (byte)input.ReadByte(); + input.Read(bytesBigEndian, 0, 4); + chars[3] = bytesBigEndian[0]; + chars[2] = bytesBigEndian[1]; + chars[1] = bytesBigEndian[2]; + chars[0] = bytesBigEndian[3]; return BitConverter.ToSingle(chars, 0); } public int ReadInt () { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + input.Read(bytesBigEndian, 0, 4); + return (bytesBigEndian[0] << 24) + + (bytesBigEndian[1] << 16) + + (bytesBigEndian[2] << 8) + + bytesBigEndian[3]; } public int ReadInt (bool optimizePositive) { diff --git a/spine-ts/webgl/demos/demos.js b/spine-ts/webgl/demos/demos.js index 4b654aa84..e121f2fe3 100644 --- a/spine-ts/webgl/demos/demos.js +++ b/spine-ts/webgl/demos/demos.js @@ -4,7 +4,6 @@ $(function () { alert("Error: " + message + "\n" + "URL:" + url + "\nLine: " + lineNo); } - spineDemos.init(); spineDemos.assetManager = new spine.SharedAssetManager("assets/"); diff --git a/spine-ts/webgl/example/barebones.html b/spine-ts/webgl/example/barebones.html new file mode 100644 index 000000000..47a450156 --- /dev/null +++ b/spine-ts/webgl/example/barebones.html @@ -0,0 +1,157 @@ + + + + + + + + \ No newline at end of file diff --git a/spine-ts/webgl/example/index.html b/spine-ts/webgl/example/index.html index ab69a6e91..a6d1abd33 100644 --- a/spine-ts/webgl/example/index.html +++ b/spine-ts/webgl/example/index.html @@ -2,9 +2,9 @@ @@ -15,21 +15,22 @@ Skin: Vertex Effect: Debug: -
+
- + \ No newline at end of file diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs index 260a90150..7a0e87cf2 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs @@ -55,7 +55,7 @@ namespace Spine.Unity.Editor { } } - void OnEnable () { + void InitializeEditor () { skeletonRenderer = serializedObject.FindProperty("skeletonRenderer"); slotName = serializedObject.FindProperty("slotName"); isTrigger = serializedObject.FindProperty("isTrigger"); @@ -64,12 +64,17 @@ namespace Spine.Unity.Editor { } public override void OnInspectorGUI () { + #if !NEW_PREFAB_SYSTEM bool isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab); #else bool isInspectingPrefab = false; #endif + // Note: when calling InitializeEditor() in OnEnable, it throws exception + // "SerializedObjectNotCreatableException: Object at index 0 is null". + InitializeEditor(); + // Try to auto-assign SkeletonRenderer field. if (skeletonRenderer.objectReferenceValue == null) { var foundSkeletonRenderer = follower.GetComponentInParent(); @@ -80,6 +85,7 @@ namespace Spine.Unity.Editor { skeletonRenderer.objectReferenceValue = foundSkeletonRenderer; serializedObject.ApplyModifiedProperties(); + InitializeEditor(); } var skeletonRendererValue = skeletonRenderer.objectReferenceValue as SkeletonRenderer; @@ -101,6 +107,7 @@ namespace Spine.Unity.Editor { EditorGUILayout.PropertyField(slotName, new GUIContent("Slot")); if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); + InitializeEditor(); #if !NEW_PREFAB_SYSTEM if (!isInspectingPrefab) rebuildRequired = true; @@ -118,6 +125,7 @@ namespace Spine.Unity.Editor { if (clearStateChanged || triggerChanged) { serializedObject.ApplyModifiedProperties(); + InitializeEditor(); if (triggerChanged) foreach (var col in follower.colliderTable.Values) col.isTrigger = isTrigger.boolValue; @@ -152,6 +160,8 @@ namespace Spine.Unity.Editor { } + if (follower.Slot == null) + follower.Initialize(false); bool hasBoneFollower = follower.GetComponent() != null; if (!hasBoneFollower) { bool buttonDisabled = follower.Slot == null; diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs index 328766fda..c87ee6262 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs @@ -72,7 +72,9 @@ namespace Spine.Unity.Editor { var component = o as SkeletonAnimation; component.timeScale = Mathf.Max(component.timeScale, 0); } + EditorGUILayout.Space(); + SkeletonRootMotionParameter(); if (!isInspectingPrefab) { if (requireRepaint) { diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs index 97b8235bc..5f9c5942a 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs @@ -33,7 +33,6 @@ using UnityEngine; using UnityEditor; -using Spine; namespace Spine.Unity.Editor { using Icons = SpineEditorUtilities.Icons; @@ -44,6 +43,11 @@ namespace Spine.Unity.Editor { public class SkeletonGraphicInspector : UnityEditor.Editor { const string SeparatorSlotNamesFieldName = "separatorSlotNames"; + const string ReloadButtonString = "Reload"; + protected GUIContent SkeletonDataAssetLabel; + static GUILayoutOption reloadButtonWidth; + static GUILayoutOption ReloadButtonWidth { get { return reloadButtonWidth = reloadButtonWidth ?? GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonString)).x + 20); } } + static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButton; } } SerializedProperty material, color; SerializedProperty skeletonDataAsset, initialSkinName; @@ -56,6 +60,7 @@ namespace Spine.Unity.Editor { SkeletonGraphic thisSkeletonGraphic; protected bool isInspectingPrefab; protected bool slotsReapplyRequired = false; + protected bool forceReloadQueued = false; protected bool TargetIsValid { get { @@ -80,6 +85,10 @@ namespace Spine.Unity.Editor { #else isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab); #endif + SpineEditorUtilities.ConfirmInitialization(); + + // Labels + SkeletonDataAssetLabel = new GUIContent("SkeletonData Asset", Icons.spine); var so = this.serializedObject; thisSkeletonGraphic = target as SkeletonGraphic; @@ -115,10 +124,34 @@ namespace Spine.Unity.Editor { } public override void OnInspectorGUI () { + + if (UnityEngine.Event.current.type == EventType.Layout) { + if (forceReloadQueued) { + forceReloadQueued = false; + foreach (var c in targets) { + SpineEditorUtilities.ReloadSkeletonDataAssetAndComponent(c as SkeletonGraphic); + } + } + else { + foreach (var c in targets) { + var component = c as SkeletonGraphic; + if (!component.IsValid) { + SpineEditorUtilities.ReinitializeComponent(component); + if (!component.IsValid) continue; + } + } + } + } + bool wasChanged = false; EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(skeletonDataAsset); + using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) { + SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel); + if (GUILayout.Button(ReloadButtonString, ReloadButtonStyle, ReloadButtonWidth)) + forceReloadQueued = true; + } + EditorGUILayout.PropertyField(material); EditorGUILayout.PropertyField(color); @@ -201,6 +234,8 @@ namespace Spine.Unity.Editor { EditorGUILayout.Space(); EditorGUILayout.PropertyField(freeze); EditorGUILayout.Space(); + SkeletonRendererInspector.SkeletonRootMotionParameter(targets); + EditorGUILayout.Space(); EditorGUILayout.LabelField("UI", EditorStyles.boldLabel); EditorGUILayout.PropertyField(raycastTarget); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs index 4ecce5385..65929f084 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs @@ -30,21 +30,125 @@ // Contributed by: Mitch Thompson using UnityEditor; +using UnityEngine; namespace Spine.Unity.Editor { [CustomEditor(typeof(SkeletonMecanim))] [CanEditMultipleObjects] public class SkeletonMecanimInspector : SkeletonRendererInspector { - protected SerializedProperty mecanimTranslator; + public static bool mecanimSettingsFoldout; + + protected SerializedProperty autoReset; + protected SerializedProperty useCustomMixMode; + protected SerializedProperty layerMixModes; + protected SerializedProperty layerBlendModes; protected override void OnEnable () { base.OnEnable(); - mecanimTranslator = serializedObject.FindProperty("translator"); + SerializedProperty mecanimTranslator = serializedObject.FindProperty("translator"); + autoReset = mecanimTranslator.FindPropertyRelative("autoReset"); + useCustomMixMode = mecanimTranslator.FindPropertyRelative("useCustomMixMode"); + layerMixModes = mecanimTranslator.FindPropertyRelative("layerMixModes"); + layerBlendModes = mecanimTranslator.FindPropertyRelative("layerBlendModes"); } protected override void DrawInspectorGUI (bool multi) { + + AddRootMotionComponentIfEnabled(); + base.DrawInspectorGUI(multi); - EditorGUILayout.PropertyField(mecanimTranslator, true); + + using (new SpineInspectorUtility.BoxScope()) { + mecanimSettingsFoldout = EditorGUILayout.Foldout(mecanimSettingsFoldout, "Mecanim Translator"); + if (mecanimSettingsFoldout) { + EditorGUILayout.PropertyField(autoReset, new GUIContent("Auto Reset", + "When set to true, the skeleton state is mixed out to setup-" + + "pose when an animation finishes, according to the " + + "animation's keyed items.")); + + EditorGUILayout.PropertyField(useCustomMixMode, new GUIContent("Custom MixMode", + "When disabled, the recommended MixMode is used according to the layer blend mode. Enable to specify a custom MixMode for each Mecanim layer.")); + + if (useCustomMixMode.hasMultipleDifferentValues || useCustomMixMode.boolValue == true) { + DrawLayerSettings(); + EditorGUILayout.Space(); + } + } + } + } + + protected void AddRootMotionComponentIfEnabled () { + foreach (var t in targets) { + var component = t as Component; + var animator = component.GetComponent(); + if (animator != null && animator.applyRootMotion) { + if (component.GetComponent() == null) { + component.gameObject.AddComponent(); + } + } + } + } + + protected void DrawLayerSettings () { + string[] layerNames = GetLayerNames(); + float widthLayerColumn = 140; + float widthMixColumn = 84; + + using (new GUILayout.HorizontalScope()) { + var rect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, EditorGUIUtility.singleLineHeight); + rect.width = widthLayerColumn; + EditorGUI.LabelField(rect, SpineInspectorUtility.TempContent("Mecanim Layer"), EditorStyles.boldLabel); + + var savedIndent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + rect.position += new Vector2(rect.width, 0); + rect.width = widthMixColumn; + EditorGUI.LabelField(rect, SpineInspectorUtility.TempContent("Mix Mode"), EditorStyles.boldLabel); + + EditorGUI.indentLevel = savedIndent; + } + + using (new SpineInspectorUtility.IndentScope()) { + int layerCount = layerMixModes.arraySize; + for (int i = 0; i < layerCount; ++i) { + using (new GUILayout.HorizontalScope()) { + string layerName = i < layerNames.Length ? layerNames[i] : ("Layer " + i); + + var rect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, EditorGUIUtility.singleLineHeight); + rect.width = widthLayerColumn; + EditorGUI.PrefixLabel(rect, SpineInspectorUtility.TempContent(layerName)); + + var savedIndent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + var mixMode = layerMixModes.GetArrayElementAtIndex(i); + var blendMode = layerBlendModes.GetArrayElementAtIndex(i); + rect.position += new Vector2(rect.width, 0); + rect.width = widthMixColumn; + EditorGUI.PropertyField(rect, mixMode, GUIContent.none); + + EditorGUI.indentLevel = savedIndent; + } + } + } + } + + protected string[] GetLayerNames () { + int maxLayerCount = 0; + int maxIndex = 0; + for (int i = 0; i < targets.Length; ++i) { + var skeletonMecanim = ((SkeletonMecanim)targets[i]); + int count = skeletonMecanim.Translator.MecanimLayerCount; + if (count > maxLayerCount) { + maxLayerCount = count; + maxIndex = i; + } + } + if (maxLayerCount == 0) + return new string[0]; + var skeletonMecanimMaxLayers = ((SkeletonMecanim)targets[maxIndex]); + return skeletonMecanimMaxLayers.Translator.MecanimLayerNames; } } } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs new file mode 100644 index 000000000..bcdbbaab3 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs @@ -0,0 +1,81 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + * + * THE SPINE RUNTIMES ARE 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using UnityEditor; +using UnityEngine; + +namespace Spine.Unity.Editor { + [CustomEditor(typeof(SkeletonMecanimRootMotion))] + [CanEditMultipleObjects] + public class SkeletonMecanimRootMotionInspector : SkeletonRootMotionBaseInspector { + protected SerializedProperty mecanimLayerFlags; + + protected GUIContent mecanimLayersLabel; + + protected override void OnEnable () { + base.OnEnable(); + mecanimLayerFlags = serializedObject.FindProperty("mecanimLayerFlags"); + + mecanimLayersLabel = new UnityEngine.GUIContent("Mecanim Layers", "Mecanim layers to apply root motion at. Defaults to the first Mecanim layer."); + } + + override public void OnInspectorGUI () { + + base.MainPropertyFields(); + MecanimLayerMaskPropertyField(); + + base.OptionalPropertyFields(); + serializedObject.ApplyModifiedProperties(); + } + + protected string[] GetLayerNames () { + int maxLayerCount = 0; + int maxIndex = 0; + for (int i = 0; i < targets.Length; ++i) { + var skeletonMecanim = ((SkeletonMecanimRootMotion)targets[i]).SkeletonMecanim; + int count = skeletonMecanim.Translator.MecanimLayerCount; + if (count > maxLayerCount) { + maxLayerCount = count; + maxIndex = i; + } + } + if (maxLayerCount == 0) + return new string[0]; + var skeletonMecanimMaxLayers = ((SkeletonMecanimRootMotion)targets[maxIndex]).SkeletonMecanim; + return skeletonMecanimMaxLayers.Translator.MecanimLayerNames; + } + + protected void MecanimLayerMaskPropertyField () { + string[] layerNames = GetLayerNames(); + if (layerNames.Length > 0) + mecanimLayerFlags.intValue = EditorGUILayout.MaskField( + mecanimLayersLabel, mecanimLayerFlags.intValue, layerNames); + } + } +} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs.meta new file mode 100644 index 000000000..04c6dcfc9 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4613924c50d66cf458f0db803776dd2f +timeCreated: 1593175106 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs index 15f8d46b8..62f50ed22 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs @@ -82,7 +82,7 @@ namespace Spine.Unity.Editor { const string ReloadButtonString = "Reload"; static GUILayoutOption reloadButtonWidth; static GUILayoutOption ReloadButtonWidth { get { return reloadButtonWidth = reloadButtonWidth ?? GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonString)).x + 20); } } - static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButtonRight; } } + static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButton; } } protected bool TargetIsValid { get { @@ -106,7 +106,6 @@ namespace Spine.Unity.Editor { #else isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab); #endif - SpineEditorUtilities.ConfirmInitialization(); // Labels @@ -173,11 +172,8 @@ namespace Spine.Unity.Editor { if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current) || AreAnyMaskMaterialsMissing()) { if (!Application.isPlaying) { - if (multi) { - foreach (var o in targets) EditorForceInitializeComponent((SkeletonRenderer)o); - } else { - EditorForceInitializeComponent((SkeletonRenderer)target); - } + foreach (var o in targets) + SpineEditorUtilities.ReinitializeComponent((SkeletonRenderer)o); SceneView.RepaintAll(); } } @@ -188,25 +184,16 @@ namespace Spine.Unity.Editor { if (Event.current.type == EventType.Layout) { if (forceReloadQueued) { forceReloadQueued = false; - if (multi) { - foreach (var c in targets) - EditorForceReloadSkeletonDataAssetAndComponent(c as SkeletonRenderer); - } else { - EditorForceReloadSkeletonDataAssetAndComponent(target as SkeletonRenderer); + foreach (var c in targets) { + SpineEditorUtilities.ReloadSkeletonDataAssetAndComponent(c as SkeletonRenderer); } } else { - if (multi) { - foreach (var c in targets) { - var component = c as SkeletonRenderer; - if (!component.valid) { - EditorForceInitializeComponent(component); - if (!component.valid) continue; - } + foreach (var c in targets) { + var component = c as SkeletonRenderer; + if (!component.valid) { + SpineEditorUtilities.ReinitializeComponent(component); + if (!component.valid) continue; } - } else { - var component = (SkeletonRenderer)target; - if (!component.valid) - EditorForceInitializeComponent(component); } } @@ -241,15 +228,8 @@ namespace Spine.Unity.Editor { #if NO_PREFAB_MESH if (isInspectingPrefab) { - if (multi) { - foreach (var c in targets) { - var component = (SkeletonRenderer)c; - MeshFilter meshFilter = component.GetComponent(); - if (meshFilter != null && meshFilter.sharedMesh != null) - meshFilter.sharedMesh = null; - } - } else { - var component = (SkeletonRenderer)target; + foreach (var c in targets) { + var component = (SkeletonRenderer)c; MeshFilter meshFilter = component.GetComponent(); if (meshFilter != null && meshFilter.sharedMesh != null) meshFilter.sharedMesh = null; @@ -286,7 +266,7 @@ namespace Spine.Unity.Editor { return; } - if (!SkeletonDataAssetIsValid(component.skeletonDataAsset)) { + if (!SpineEditorUtilities.SkeletonDataAssetIsValid(component.skeletonDataAsset)) { EditorGUILayout.HelpBox("Skeleton Data Asset error. Please check Skeleton Data Asset.", MessageType.Error); return; } @@ -414,6 +394,48 @@ namespace Spine.Unity.Editor { } } + protected void SkeletonRootMotionParameter() { + SkeletonRootMotionParameter(targets); + } + + public static void SkeletonRootMotionParameter(Object[] targets) { + int rootMotionComponentCount = 0; + foreach (var t in targets) { + var component = t as Component; + if (component.GetComponent() != null) { + ++rootMotionComponentCount; + } + } + bool allHaveRootMotion = rootMotionComponentCount == targets.Length; + bool anyHaveRootMotion = rootMotionComponentCount > 0; + + using (new GUILayout.HorizontalScope()) { + EditorGUILayout.PrefixLabel("Root Motion"); + + if (!allHaveRootMotion) { + if (GUILayout.Button(SpineInspectorUtility.TempContent("Add Component", Icons.constraintTransform), GUILayout.MaxWidth(130), GUILayout.Height(18))) { + foreach (var t in targets) { + var component = t as Component; + if (component.GetComponent() == null) { + component.gameObject.AddComponent(); + } + } + } + } + if (anyHaveRootMotion) { + if (GUILayout.Button(SpineInspectorUtility.TempContent("Remove Component", Icons.constraintTransform), GUILayout.MaxWidth(140), GUILayout.Height(18))) { + foreach (var t in targets) { + var component = t as Component; + var rootMotionComponent = component.GetComponent(); + if (rootMotionComponent != null) { + DestroyImmediate(rootMotionComponent); + } + } + } + } + } + } + public static void SetSeparatorSlotNames (SkeletonRenderer skeletonRenderer, string[] newSlotNames) { var field = SpineInspectorUtility.GetNonPublicField(typeof(SkeletonRenderer), SeparatorSlotNamesFieldName); field.SetValue(skeletonRenderer, newSlotNames); @@ -535,38 +557,6 @@ namespace Spine.Unity.Editor { return false; } - static void EditorForceReloadSkeletonDataAssetAndComponent (SkeletonRenderer component) { - if (component == null) return; - - // Clear all and reload. - if (component.skeletonDataAsset != null) { - foreach (AtlasAssetBase aa in component.skeletonDataAsset.atlasAssets) { - if (aa != null) aa.Clear(); - } - component.skeletonDataAsset.Clear(); - } - component.skeletonDataAsset.GetSkeletonData(true); - - // Reinitialize. - EditorForceInitializeComponent(component); - } - - static void EditorForceInitializeComponent (SkeletonRenderer component) { - if (component == null) return; - if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; - component.Initialize(true); - - #if BUILT_IN_SPRITE_MASK_COMPONENT - SpineMaskUtilities.EditorAssignSpriteMaskMaterials(component); - #endif - - component.LateUpdate(); - } - - static bool SkeletonDataAssetIsValid (SkeletonDataAsset asset) { - return asset != null && asset.GetSkeletonData(quiet: true) != null; - } - bool AreAnyMaskMaterialsMissing() { #if BUILT_IN_SPRITE_MASK_COMPONENT foreach (var o in targets) { @@ -584,13 +574,13 @@ namespace Spine.Unity.Editor { static void EditorSetMaskMaterials(SkeletonRenderer component, SpriteMaskInteraction maskType) { if (component == null) return; - if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; + if (!SpineEditorUtilities.SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; SpineMaskUtilities.EditorInitMaskMaterials(component, component.maskMaterials, maskType); } static void EditorDeleteMaskMaterials(SkeletonRenderer component, SpriteMaskInteraction maskType) { if (component == null) return; - if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; + if (!SpineEditorUtilities.SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; SpineMaskUtilities.EditorDeleteMaskMaterials(component.maskMaterials, maskType); } #endif diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs new file mode 100644 index 000000000..dd622e479 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs @@ -0,0 +1,92 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + * + * THE SPINE RUNTIMES ARE 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using UnityEditor; +using UnityEngine; + +namespace Spine.Unity.Editor { + [CustomEditor(typeof(SkeletonRootMotionBase))] + [CanEditMultipleObjects] + public class SkeletonRootMotionBaseInspector : UnityEditor.Editor { + protected SerializedProperty rootMotionBoneName; + protected SerializedProperty transformPositionX; + protected SerializedProperty transformPositionY; + protected SerializedProperty rigidBody2D; + protected SerializedProperty rigidBody; + + protected GUIContent rootMotionBoneNameLabel; + protected GUIContent transformPositionXLabel; + protected GUIContent transformPositionYLabel; + protected GUIContent rigidBody2DLabel; + protected GUIContent rigidBodyLabel; + + protected virtual void OnEnable () { + + rootMotionBoneName = serializedObject.FindProperty("rootMotionBoneName"); + transformPositionX = serializedObject.FindProperty("transformPositionX"); + transformPositionY = serializedObject.FindProperty("transformPositionY"); + rigidBody2D = serializedObject.FindProperty("rigidBody2D"); + rigidBody = serializedObject.FindProperty("rigidBody"); + + rootMotionBoneNameLabel = new UnityEngine.GUIContent("Root Motion Bone", "The bone to take the motion from."); + transformPositionXLabel = new UnityEngine.GUIContent("X", "Root transform position (X)"); + transformPositionYLabel = new UnityEngine.GUIContent("Y", "Use the Y-movement of the bone."); + rigidBody2DLabel = new UnityEngine.GUIContent("Rigidbody2D", + "Optional Rigidbody2D: Assign a Rigidbody2D here if you want " + + " to apply the root motion to the rigidbody instead of the Transform." + + "\n\n" + + "Note that animation and physics updates are not always in sync." + + "Some jitter may result at certain framerates."); + rigidBodyLabel = new UnityEngine.GUIContent("Rigidbody", + "Optional Rigidbody: Assign a Rigidbody here if you want " + + " to apply the root motion to the rigidbody instead of the Transform." + + "\n\n" + + "Note that animation and physics updates are not always in sync." + + "Some jitter may result at certain framerates."); + } + + public override void OnInspectorGUI () { + MainPropertyFields(); + OptionalPropertyFields(); + serializedObject.ApplyModifiedProperties(); + } + + protected virtual void MainPropertyFields () { + EditorGUILayout.PropertyField(rootMotionBoneName, rootMotionBoneNameLabel); + EditorGUILayout.PropertyField(transformPositionX, transformPositionXLabel); + EditorGUILayout.PropertyField(transformPositionY, transformPositionYLabel); + } + + protected virtual void OptionalPropertyFields () { + //EditorGUILayout.LabelField("Optional", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(rigidBody2D, rigidBody2DLabel); + EditorGUILayout.PropertyField(rigidBody, rigidBodyLabel); + } + } +} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs.meta new file mode 100644 index 000000000..030444885 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f2cba83baf6afdf44a996e40017c6325 +timeCreated: 1593175106 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs new file mode 100644 index 000000000..2c9650656 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs @@ -0,0 +1,79 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + * + * THE SPINE RUNTIMES ARE 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using UnityEditor; +using UnityEngine; + +namespace Spine.Unity.Editor { + [CustomEditor(typeof(SkeletonRootMotion))] + [CanEditMultipleObjects] + public class SkeletonRootMotionInspector : SkeletonRootMotionBaseInspector { + protected SerializedProperty animationTrackFlags; + protected GUIContent animationTrackFlagsLabel; + + string[] TrackNames; + + protected override void OnEnable () { + base.OnEnable(); + + animationTrackFlags = serializedObject.FindProperty("animationTrackFlags"); + animationTrackFlagsLabel = new UnityEngine.GUIContent("Animation Tracks", + "Animation tracks to apply root motion at. Defaults to the first" + + " animation track (index 0)."); + } + + override public void OnInspectorGUI () { + + base.MainPropertyFields(); + AnimationTracksPropertyField(); + + base.OptionalPropertyFields(); + serializedObject.ApplyModifiedProperties(); + } + + protected void AnimationTracksPropertyField () { + + if (TrackNames == null) { + InitTrackNames(); + + } + + animationTrackFlags.intValue = EditorGUILayout.MaskField( + animationTrackFlagsLabel, animationTrackFlags.intValue, TrackNames); + } + + protected void InitTrackNames () { + int numEntries = 32; + TrackNames = new string[numEntries]; + for (int i = 0; i < numEntries; ++i) { + TrackNames[i] = string.Format("Track {0}", i); + } + } + } +} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs.meta new file mode 100644 index 000000000..7c357a899 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e4836100aed984c4a9af11d39c63cb6b +timeCreated: 1593183609 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Shaders/SpineSpriteShaderGUI.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Shaders/SpineSpriteShaderGUI.cs index 679500e3e..bd1267232 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Shaders/SpineSpriteShaderGUI.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Shaders/SpineSpriteShaderGUI.cs @@ -169,7 +169,7 @@ public class SpineSpriteShaderGUI : SpineShaderWithOutlineGUI { static GUIContent _rimPowerText = new GUIContent("Rim Power"); static GUIContent _specularToggleText = new GUIContent("Specular", "Enable Specular."); static GUIContent _colorAdjustmentToggleText = new GUIContent("Color Adjustment", "Enable material color adjustment."); - static GUIContent _colorAdjustmentColorText = new GUIContent("Overlay Color"); + static GUIContent _colorAdjustmentColorText = new GUIContent("Overlay Color & Alpha"); static GUIContent _colorAdjustmentHueText = new GUIContent("Hue"); static GUIContent _colorAdjustmentSaturationText = new GUIContent("Saturation"); static GUIContent _colorAdjustmentBrightnessText = new GUIContent("Brightness"); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs index 877cd3ae6..fe841c71c 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs @@ -46,6 +46,10 @@ #define NEW_PREFERENCES_SETTINGS_PROVIDER #endif +#if UNITY_2017_1_OR_NEWER +#define BUILT_IN_SPRITE_MASK_COMPONENT +#endif + using UnityEngine; using UnityEditor; using System.Collections.Generic; @@ -189,6 +193,62 @@ namespace Spine.Unity.Editor { } } + public static void ReloadSkeletonDataAssetAndComponent (SkeletonRenderer component) { + if (component == null) return; + ReloadSkeletonDataAsset(component.skeletonDataAsset); + ReinitializeComponent(component); + } + + public static void ReloadSkeletonDataAssetAndComponent (SkeletonGraphic component) { + if (component == null) return; + ReloadSkeletonDataAsset(component.skeletonDataAsset); + // Reinitialize. + ReinitializeComponent(component); + } + + public static void ReloadSkeletonDataAsset (SkeletonDataAsset skeletonDataAsset) { + if (skeletonDataAsset != null) { + foreach (AtlasAssetBase aa in skeletonDataAsset.atlasAssets) { + if (aa != null) aa.Clear(); + } + skeletonDataAsset.Clear(); + } + skeletonDataAsset.GetSkeletonData(true); + } + + public static void ReinitializeComponent (SkeletonRenderer component) { + if (component == null) return; + if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; + + var stateComponent = component as IAnimationStateComponent; + AnimationState oldAnimationState = null; + if (stateComponent != null) { + oldAnimationState = stateComponent.AnimationState; + } + + component.Initialize(true); // implicitly clears any subscribers + + if (oldAnimationState != null) { + stateComponent.AnimationState.AssignEventSubscribersFrom(oldAnimationState); + } + + #if BUILT_IN_SPRITE_MASK_COMPONENT + SpineMaskUtilities.EditorAssignSpriteMaskMaterials(component); + #endif + component.LateUpdate(); + } + + public static void ReinitializeComponent (SkeletonGraphic component) { + if (component == null) return; + if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; + component.Initialize(true); + component.LateUpdate(); + } + + public static bool SkeletonDataAssetIsValid (SkeletonDataAsset asset) { + return asset != null && asset.GetSkeletonData(quiet: true) != null; + } + public static bool IssueWarningsForUnrecommendedTextureSettings(string texturePath) { TextureImporter texImporter = (TextureImporter)TextureImporter.GetAtPath(texturePath); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs index 9c5fd1bf0..19712597c 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs @@ -27,6 +27,10 @@ * THE SPINE RUNTIMES, 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 UnityEngine; using UnityEditor; @@ -81,7 +85,18 @@ namespace Spine.Unity.Examples { // Restore mesh part for undo logic after undo of "Add Parts Renderer". // Triggers regeneration and assignment of the mesh filter's mesh. - if (component.GetComponent() && component.GetComponent().sharedMesh == null) { + + bool isMeshFilterAlwaysNull = false; + #if UNITY_EDITOR && NEW_PREFAB_SYSTEM + // Don't store mesh or material at the prefab, otherwise it will permanently reload + var prefabType = UnityEditor.PrefabUtility.GetPrefabAssetType(component); + if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(component) && + (prefabType == UnityEditor.PrefabAssetType.Regular || prefabType == UnityEditor.PrefabAssetType.Variant)) { + isMeshFilterAlwaysNull = true; + } + #endif + + if (!isMeshFilterAlwaysNull && component.GetComponent() && component.GetComponent().sharedMesh == null) { component.OnDisable(); component.OnEnable(); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs index 63e5e30d9..570a2cc78 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs @@ -106,7 +106,12 @@ namespace Spine.Unity { ) return; - DisposeColliders(); + slot = null; + currentAttachment = null; + currentAttachmentName = null; + currentCollider = null; + colliderTable.Clear(); + nameTable.Clear(); var skeleton = skeletonRenderer.skeleton; slot = skeleton.FindSlot(slotName); @@ -118,13 +123,16 @@ namespace Spine.Unity { return; } + int requiredCollidersCount = 0; + var colliders = GetComponents(); if (this.gameObject.activeInHierarchy) { foreach (var skin in skeleton.Data.Skins) - AddSkin(skin, slotIndex); + AddCollidersForSkin(skin, slotIndex, colliders, ref requiredCollidersCount); if (skeleton.skin != null) - AddSkin(skeleton.skin, slotIndex); + AddCollidersForSkin(skeleton.skin, slotIndex, colliders, ref requiredCollidersCount); } + DisposeExcessCollidersAfter(requiredCollidersCount); if (BoundingBoxFollower.DebugMessages) { bool valid = colliderTable.Count != 0; @@ -137,7 +145,7 @@ namespace Spine.Unity { } } - void AddSkin (Skin skin, int slotIndex) { + void AddCollidersForSkin (Skin skin, int slotIndex, PolygonCollider2D[] previousColliders, ref int collidersCount) { if (skin == null) return; var skinEntries = new List(); skin.GetAttachments(slotIndex, skinEntries); @@ -151,8 +159,11 @@ namespace Spine.Unity { if (boundingBoxAttachment != null) { if (!colliderTable.ContainsKey(boundingBoxAttachment)) { - var bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(boundingBoxAttachment, slot, gameObject, isTrigger); - + var bbCollider = collidersCount < previousColliders.Length ? + previousColliders[collidersCount] : gameObject.AddComponent(); + ++collidersCount; + SkeletonUtility.SetColliderPointsLocal(bbCollider, slot, boundingBoxAttachment); + bbCollider.isTrigger = isTrigger; bbCollider.enabled = false; bbCollider.hideFlags = HideFlags.NotEditable; bbCollider.isTrigger = IsTrigger; @@ -178,33 +189,21 @@ namespace Spine.Unity { currentCollider = null; } - void DisposeColliders () { + void DisposeExcessCollidersAfter (int requiredCount) { var colliders = GetComponents(); if (colliders.Length == 0) return; - if (Application.isEditor) { - if (Application.isPlaying) { - foreach (var c in colliders) { - if (c != null) - Destroy(c); - } - } else { - foreach (var c in colliders) - if (c != null) - DestroyImmediate(c); + for (int i = requiredCount; i < colliders.Length; ++i) { + var collider = colliders[i]; + if (collider != null) { +#if UNITY_EDITOR + if (Application.isEditor && !Application.isPlaying) + DestroyImmediate(collider); + else +#endif + Destroy(collider); } - } else { - foreach (PolygonCollider2D c in colliders) - if (c != null) - Destroy(c); } - - slot = null; - currentAttachment = null; - currentAttachmentName = null; - currentCollider = null; - colliderTable.Clear(); - nameTable.Clear(); } void LateUpdate () { diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion.meta new file mode 100644 index 000000000..d8d3bd381 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 2b957aa69dae9f948bacdeec549d28ea +folderAsset: yes +timeCreated: 1593173800 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs new file mode 100644 index 000000000..e49e8a486 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs @@ -0,0 +1,100 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + * + * THE SPINE RUNTIMES ARE 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using UnityEngine; +using System.Collections.Generic; +using Spine.Unity.AnimationTools; + +namespace Spine.Unity { + + /// + /// Add this component to a SkeletonMecanim GameObject + /// to turn motion of a selected root bone into Transform or RigidBody motion. + /// Local bone translation movement is used as motion. + /// All top-level bones of the skeleton are moved to compensate the root + /// motion bone location, keeping the distance relationship between bones intact. + /// + /// + /// Only compatible with SkeletonMecanim. + /// For SkeletonAnimation or SkeletonGraphic please use + /// SkeletonRootMotion instead. + /// + public class SkeletonMecanimRootMotion : SkeletonRootMotionBase { + #region Inspector + const int DefaultMecanimLayerFlags = -1; + public int mecanimLayerFlags = DefaultMecanimLayerFlags; + #endregion + + protected Vector2 movementDelta; + + SkeletonMecanim skeletonMecanim; + public SkeletonMecanim SkeletonMecanim { + get { + return skeletonMecanim ? skeletonMecanim : skeletonMecanim = GetComponent(); + } + } + + protected override void Reset () { + base.Reset(); + mecanimLayerFlags = DefaultMecanimLayerFlags; + } + + protected override void Start () { + base.Start(); + skeletonMecanim = GetComponent(); + if (skeletonMecanim) { + skeletonMecanim.Translator.OnClipApplied -= OnClipApplied; + skeletonMecanim.Translator.OnClipApplied += OnClipApplied; + } + } + + void OnClipApplied(Spine.Animation clip, int layerIndex, float weight, + float time, float lastTime, bool playsBackward) { + + if (((mecanimLayerFlags & 1< + /// Add this component to a SkeletonAnimation or SkeletonGraphic GameObject + /// to turn motion of a selected root bone into Transform or RigidBody motion. + /// Local bone translation movement is used as motion. + /// All top-level bones of the skeleton are moved to compensate the root + /// motion bone location, keeping the distance relationship between bones intact. + /// + /// + /// Only compatible with SkeletonAnimation (or other components that implement + /// ISkeletonComponent, ISkeletonAnimation and IAnimationStateComponent). + /// For SkeletonMecanim please use + /// SkeletonMecanimRootMotion instead. + /// + public class SkeletonRootMotion : SkeletonRootMotionBase { + #region Inspector + const int DefaultAnimationTrackFlags = -1; + public int animationTrackFlags = DefaultAnimationTrackFlags; + #endregion + + AnimationState animationState; + Canvas canvas; + + protected override float AdditionalScale { + get { + return canvas ? canvas.referencePixelsPerUnit: 1.0f; + } + } + + protected override void Reset () { + base.Reset(); + animationTrackFlags = DefaultAnimationTrackFlags; + } + + protected override void Start () { + base.Start(); + var animstateComponent = skeletonComponent as IAnimationStateComponent; + this.animationState = (animstateComponent != null) ? animstateComponent.AnimationState : null; + + if (this.GetComponent() != null) { + canvas = this.GetComponentInParent(); + } + } + + protected override Vector2 CalculateAnimationsMovementDelta () { + Vector2 localDelta = Vector2.zero; + int trackCount = animationState.Tracks.Count; + + for (int trackIndex = 0; trackIndex < trackCount; ++trackIndex) { + // note: animationTrackFlags != -1 below covers trackIndex >= 32, + // with -1 corresponding to entry "everything" of the dropdown list. + if (animationTrackFlags != -1 && (animationTrackFlags & 1 << trackIndex) == 0) + continue; + + TrackEntry track = animationState.GetCurrent(trackIndex); + TrackEntry next = null; + while (track != null) { + var animation = track.Animation; + var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex); + if (timeline != null) { + var currentDelta = GetTrackMovementDelta(track, timeline, animation, next); + localDelta += currentDelta; + } + // Traverse mixingFrom chain. + next = track; + track = track.mixingFrom; + } + } + return localDelta; + } + + Vector2 GetTrackMovementDelta (TrackEntry track, TranslateTimeline timeline, + Animation animation, TrackEntry next) { + + float start = track.animationLast; + float end = track.AnimationTime; + Vector2 currentDelta = GetTimelineMovementDelta(start, end, timeline, animation); + + ApplyMixAlphaToDelta(ref currentDelta, next, track); + return currentDelta; + } + + void ApplyMixAlphaToDelta (ref Vector2 currentDelta, TrackEntry next, TrackEntry track) { + // Apply mix alpha to the delta position (based on AnimationState.cs). + float mix; + if (next != null) { + if (next.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. + mix = 1; + } + else { + mix = next.mixTime / next.mixDuration; + if (mix > 1) mix = 1; + } + float mixAndAlpha = track.alpha * next.interruptAlpha * (1 - mix); + currentDelta *= mixAndAlpha; + } + else { + if (track.mixDuration == 0) { + mix = 1; + } + else { + mix = track.alpha * (track.mixTime / track.mixDuration); + if (mix > 1) mix = 1; + } + currentDelta *= mix; + } + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs.meta new file mode 100644 index 000000000..bef9e2e4f --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f21c9538588898a45a3da22bf4779ab3 +timeCreated: 1591121072 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs new file mode 100644 index 000000000..5b1e50dbb --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs @@ -0,0 +1,184 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + * + * THE SPINE RUNTIMES ARE 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using UnityEngine; +using System.Collections.Generic; +using Spine.Unity.AnimationTools; + +namespace Spine.Unity { + + /// + /// Base class for skeleton root motion components. + /// + abstract public class SkeletonRootMotionBase : MonoBehaviour { + #region Inspector + + [SpineBone] + [SerializeField] + protected string rootMotionBoneName = "root"; + public bool transformPositionX = true; + public bool transformPositionY = true; + + [Header("Optional")] + public Rigidbody2D rigidBody2D; + public Rigidbody rigidBody; + + public bool UsesRigidbody { + get { return rigidBody != null || rigidBody2D != null; } + } + #endregion + + protected ISkeletonComponent skeletonComponent; + protected Bone rootMotionBone; + protected int rootMotionBoneIndex; + protected List topLevelBones = new List(); + protected Vector2 rigidbodyDisplacement; + + protected virtual void Reset () { + FindRigidbodyComponent(); + } + + protected virtual void Start () { + skeletonComponent = GetComponent(); + GatherTopLevelBones(); + SetRootMotionBone(rootMotionBoneName); + + var skeletonAnimation = skeletonComponent as ISkeletonAnimation; + if (skeletonAnimation != null) + skeletonAnimation.UpdateLocal += HandleUpdateLocal; + } + + abstract protected Vector2 CalculateAnimationsMovementDelta (); + + protected virtual float AdditionalScale { get { return 1.0f; } } + + protected Vector2 GetTimelineMovementDelta (float startTime, float endTime, + TranslateTimeline timeline, Animation animation) { + + Vector2 currentDelta; + if (startTime > endTime) // Looped + currentDelta = (timeline.Evaluate(animation.duration) - timeline.Evaluate(startTime)) + + (timeline.Evaluate(endTime) - timeline.Evaluate(0)); + else if (startTime != endTime) // Non-looped + currentDelta = timeline.Evaluate(endTime) - timeline.Evaluate(startTime); + else + currentDelta = Vector2.zero; + return currentDelta; + } + + void GatherTopLevelBones () { + topLevelBones.Clear(); + var skeleton = skeletonComponent.Skeleton; + foreach (var bone in skeleton.Bones) { + if (bone.Parent == null) + topLevelBones.Add(bone); + } + } + + public void SetRootMotionBone (string name) { + var skeleton = skeletonComponent.Skeleton; + int index = skeleton.FindBoneIndex(name); + if (index >= 0) { + this.rootMotionBoneIndex = index; + this.rootMotionBone = skeleton.bones.Items[index]; + } + else { + Debug.Log("Bone named \"" + name + "\" could not be found."); + this.rootMotionBoneIndex = 0; + this.rootMotionBone = skeleton.RootBone; + } + } + + void HandleUpdateLocal (ISkeletonAnimation animatedSkeletonComponent) { + if (!this.isActiveAndEnabled) + return; // Root motion is only applied when component is enabled. + + var movementDelta = CalculateAnimationsMovementDelta(); + AdjustMovementDeltaToConfiguration(ref movementDelta, animatedSkeletonComponent.Skeleton); + ApplyRootMotion(movementDelta); + } + + void AdjustMovementDeltaToConfiguration (ref Vector2 localDelta, Skeleton skeleton) { + if (skeleton.ScaleX < 0) localDelta.x = -localDelta.x; + if (skeleton.ScaleY < 0) localDelta.y = -localDelta.y; + if (!transformPositionX) localDelta.x = 0f; + if (!transformPositionY) localDelta.y = 0f; + } + + void ApplyRootMotion (Vector2 localDelta) { + localDelta *= AdditionalScale; + // Apply root motion to Transform or RigidBody; + if (UsesRigidbody) { + rigidbodyDisplacement += (Vector2)transform.TransformVector(localDelta); + // Accumulated displacement is applied on the next Physics update (FixedUpdate) + } + else { + + transform.position += transform.TransformVector(localDelta); + } + + // Move top level bones in opposite direction of the root motion bone + foreach (var topLevelBone in topLevelBones) { + if (transformPositionX) topLevelBone.x -= rootMotionBone.x; + if (transformPositionY) topLevelBone.y -= rootMotionBone.y; + } + } + + protected virtual void FixedUpdate () { + if (!this.isActiveAndEnabled) + return; // Root motion is only applied when component is enabled. + + if(rigidBody2D != null) { + rigidBody2D.MovePosition(new Vector2(transform.position.x, transform.position.y) + + rigidbodyDisplacement); + } + if (rigidBody != null) { + rigidBody.MovePosition(transform.position + + new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0)); + } + rigidbodyDisplacement = Vector2.zero; + } + + protected virtual void OnDisable () { + rigidbodyDisplacement = Vector2.zero; + } + + protected void FindRigidbodyComponent () { + rigidBody2D = this.GetComponent(); + if (!rigidBody2D) + rigidBody = this.GetComponent(); + + if (!rigidBody2D && !rigidBody) { + rigidBody2D = this.GetComponentInParent(); + if (!rigidBody2D) + rigidBody = this.GetComponentInParent(); + } + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs.meta new file mode 100644 index 000000000..719fd629f --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fc23a4f220b20024ab0592719f92587d +timeCreated: 1592849332 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: 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 2956a56d6..3ea70fbea 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -210,6 +210,13 @@ namespace Spine.Unity { if (allowMultipleCanvasRenderers) canvasRenderer.Clear(); } + protected override void OnDisable () { + base.OnDisable(); + foreach (var canvasRenderer in canvasRenderers) { + canvasRenderer.Clear(); + } + } + public virtual void Update () { #if UNITY_EDITOR if (!Application.isPlaying) { @@ -426,7 +433,7 @@ namespace Spine.Unity { ScaleY = this.initialFlipY ? -1 : 1 }; - meshBuffers = new DoubleBuffered(); + InitMeshBuffers(); baseTexture = skeletonDataAsset.atlasAssets[0].PrimaryMaterial.mainTexture; canvasRenderer.SetTexture(this.mainTexture); // Needed for overwriting initializations. @@ -478,6 +485,16 @@ namespace Spine.Unity { } #endregion + protected void InitMeshBuffers () { + if (meshBuffers != null) { + meshBuffers.GetNext().Clear(); + meshBuffers.GetNext().Clear(); + } + else { + meshBuffers = new DoubleBuffered(); + } + } + protected void UpdateMeshSingleCanvasRenderer () { if (canvasRenderers.Count > 0) DisableUnusedCanvasRenderers(usedCount : 0); diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs index 71690ec46..6f72be6cc 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs @@ -125,10 +125,17 @@ namespace Spine.Unity { public class MecanimTranslator { #region Inspector public bool autoReset = true; + public bool useCustomMixMode = true; public MixMode[] layerMixModes = new MixMode[0]; public MixBlend[] layerBlendModes = new MixBlend[0]; #endregion + public delegate void OnClipAppliedDelegate (Spine.Animation clip, int layerIndex, float weight, + float time, float lastTime, bool playsBackward); + protected event OnClipAppliedDelegate _OnClipApplied; + + public event OnClipAppliedDelegate OnClipApplied { add { _OnClipApplied += value; } remove { _OnClipApplied -= value; } } + public enum MixMode { AlwaysMix, MixNext, Hard } readonly Dictionary animationTable = new Dictionary(IntEqualityComparer.Instance); @@ -157,6 +164,26 @@ namespace Spine.Unity { Animator animator; public Animator Animator { get { return this.animator; } } + public int MecanimLayerCount { + get { + if (!animator) + return 0; + return animator.layerCount; + } + } + + public string[] MecanimLayerNames { + get { + if (!animator) + return new string[0]; + string[] layerNames = new string[animator.layerCount]; + for (int i = 0; i < animator.layerCount; ++i) { + layerNames[i] = animator.GetLayerName(i); + } + return layerNames; + } + } + public void Initialize(Animator animator, SkeletonDataAsset skeletonDataAsset) { this.animator = animator; @@ -171,24 +198,87 @@ namespace Spine.Unity { ClearClipInfosForLayers(); } - public void Apply (Skeleton skeleton) { - if (layerMixModes.Length < animator.layerCount) { - System.Array.Resize(ref layerMixModes, animator.layerCount); - layerMixModes[animator.layerCount-1] = MixMode.MixNext; - } + private bool ApplyAnimation (Skeleton skeleton, AnimatorClipInfo info, AnimatorStateInfo stateInfo, + int layerIndex, float layerWeight, MixBlend layerBlendMode, bool useClipWeight1 = false) { + float weight = info.weight * layerWeight; + if (weight == 0) + return false; + var clip = GetAnimation(info.clip); + if (clip == null) + return false; + + var time = AnimationTime(stateInfo.normalizedTime, info.clip.length, + info.clip.isLooping, stateInfo.speed < 0); + weight = useClipWeight1 ? layerWeight : weight; + clip.Apply(skeleton, 0, time, info.clip.isLooping, null, + weight, layerBlendMode, MixDirection.In); + if (_OnClipApplied != null) + OnClipAppliedCallback(clip, stateInfo, layerIndex, time, info.clip.isLooping, weight); + return true; + } + + private bool ApplyInterruptionAnimation (Skeleton skeleton, + bool interpolateWeightTo1, AnimatorClipInfo info, AnimatorStateInfo stateInfo, + int layerIndex, float layerWeight, MixBlend layerBlendMode, float interruptingClipTimeAddition, + bool useClipWeight1 = false) { + + float clipWeight = interpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; + float weight = clipWeight * layerWeight; + if (weight == 0) + return false; + + var clip = GetAnimation(info.clip); + if (clip == null) + return false; + + var time = AnimationTime(stateInfo.normalizedTime + interruptingClipTimeAddition, + info.clip.length, stateInfo.speed < 0); + weight = useClipWeight1 ? layerWeight : weight; + clip.Apply(skeleton, 0, time, info.clip.isLooping, null, + weight, layerBlendMode, MixDirection.In); + if (_OnClipApplied != null) { + OnClipAppliedCallback(clip, stateInfo, layerIndex, time, info.clip.isLooping, weight); + } + return true; + } + + private void OnClipAppliedCallback (Spine.Animation clip, AnimatorStateInfo stateInfo, + int layerIndex, float time, bool isLooping, float weight) { + + float clipDuration = clip.duration == 0 ? 1 : clip.duration; + float speedFactor = stateInfo.speedMultiplier * stateInfo.speed; + float lastTime = time - (Time.deltaTime * speedFactor); + if (isLooping && clip.duration != 0) { + time %= clip.duration; + lastTime %= clip.duration; + } + _OnClipApplied(clip, layerIndex, weight, time, lastTime, speedFactor < 0); + } + + public void Apply (Skeleton skeleton) { #if UNITY_EDITOR if (!Application.isPlaying) { GetLayerBlendModes(); } #endif + + if (layerMixModes.Length < animator.layerCount) { + int oldSize = layerMixModes.Length; + System.Array.Resize(ref layerMixModes, animator.layerCount); + for (int layer = oldSize; layer < animator.layerCount; ++layer) { + bool isAdditiveLayer = false; + if (layer < layerBlendModes.Length) + isAdditiveLayer = layerBlendModes[layer] == MixBlend.Add; + layerMixModes[layer] = isAdditiveLayer ? MixMode.MixNext : MixMode.AlwaysMix; + } + } + InitClipInfosForLayers(); for (int layer = 0, n = animator.layerCount; layer < n; layer++) { GetStateUpdatesFromAnimator(layer); } - //skeleton.Update(Time.deltaTime); // Doesn't actually do anything, currently. (Spine 3.6). - // Clear Previous if (autoReset) { var previousAnimations = this.previousAnimations; @@ -257,56 +347,41 @@ namespace Spine.Unity { int clipInfoCount, nextClipInfoCount, interruptingClipInfoCount; IList clipInfo, nextClipInfo, interruptingClipInfo; - bool shallInterpolateWeightTo1; + bool interpolateWeightTo1; GetAnimatorClipInfos(layer, out isInterruptionActive, out clipInfoCount, out nextClipInfoCount, out interruptingClipInfoCount, - out clipInfo, out nextClipInfo, out interruptingClipInfo, out shallInterpolateWeightTo1); + out clipInfo, out nextClipInfo, out interruptingClipInfo, out interpolateWeightTo1); - MixMode mode = layerMixModes[layer]; MixBlend layerBlendMode = (layer < layerBlendModes.Length) ? layerBlendModes[layer] : MixBlend.Replace; + MixMode mode = GetMixMode(layer, layerBlendMode); if (mode == MixMode.AlwaysMix) { // Always use Mix instead of Applying the first non-zero weighted clip. for (int c = 0; c < clipInfoCount; c++) { - var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, info.clip.isLooping, stateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode); } if (hasNext) { for (int c = 0; c < nextClipInfoCount; c++) { - var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode); } } if (isInterruptionActive) { for (int c = 0; c < interruptingClipInfoCount; c++) { - var info = interruptingClipInfo[c]; - float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; - float weight = clipWeight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), - info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + ApplyInterruptionAnimation(skeleton, interpolateWeightTo1, + interruptingClipInfo[c], interruptingStateInfo, + layer, layerWeight, layerBlendMode, interruptingClipTimeAddition); } } } else { // case MixNext || Hard // Apply first non-zero weighted clip int c = 0; for (; c < clipInfoCount; c++) { - var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, info.clip.isLooping, stateInfo.speed < 0), info.clip.isLooping, null, 1f, layerBlendMode, MixDirection.In); + if (!ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode, useClipWeight1:true)) + continue; ++c; break; } // Mix the rest for (; c < clipInfoCount; c++) { - var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, info.clip.isLooping, stateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode); } c = 0; @@ -314,19 +389,15 @@ namespace Spine.Unity { // Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights) if (mode == MixMode.Hard) { for (; c < nextClipInfoCount; c++) { - var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), info.clip.isLooping, null, 1f, layerBlendMode, MixDirection.In); + if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode, useClipWeight1:true)) + continue; ++c; break; } } // Mix the rest for (; c < nextClipInfoCount; c++) { - var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode)) + continue; } } @@ -335,23 +406,19 @@ namespace Spine.Unity { // Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights) if (mode == MixMode.Hard) { for (; c < interruptingClipInfoCount; c++) { - var info = interruptingClipInfo[c]; - float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; - float weight = clipWeight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), info.clip.isLooping, null, 1f, layerBlendMode, MixDirection.In); - ++c; break; + if (ApplyInterruptionAnimation(skeleton, interpolateWeightTo1, + interruptingClipInfo[c], interruptingStateInfo, + layer, layerWeight, layerBlendMode, interruptingClipTimeAddition, useClipWeight1:true)) { + + ++c; break; + } } } // Mix the rest for (; c < interruptingClipInfoCount; c++) { - var info = interruptingClipInfo[c]; - float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; - float weight = clipWeight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + ApplyInterruptionAnimation(skeleton, interpolateWeightTo1, + interruptingClipInfo[c], interruptingStateInfo, + layer, layerWeight, layerBlendMode, interruptingClipTimeAddition); } } } @@ -359,9 +426,7 @@ namespace Spine.Unity { } static float AnimationTime (float normalizedTime, float clipLength, bool loop, bool reversed) { - if (reversed) - normalizedTime = (1 - normalizedTime + (int)normalizedTime) + (int)normalizedTime; - float time = normalizedTime * clipLength; + float time = AnimationTime(normalizedTime, clipLength, reversed); if (loop) return time; const float EndSnapEpsilon = 1f / 30f; // Workaround for end-duration keys not being applied. return (clipLength - time < EndSnapEpsilon) ? clipLength : time; // return a time snapped to clipLength; @@ -398,7 +463,24 @@ namespace Spine.Unity { } } - #if UNITY_EDITOR + private MixMode GetMixMode (int layer, MixBlend layerBlendMode) { + if (useCustomMixMode) { + MixMode mode = layerMixModes[layer]; + // Note: at additive blending it makes no sense to use constant weight 1 at a fadeout anim add1 as + // with override layers, so we use AlwaysMix instead to use the proper weights. + // AlwaysMix leads to the expected result = lower_layer + lerp(add1, add2, transition_weight). + if (layerBlendMode == MixBlend.Add && mode == MixMode.MixNext) { + mode = MixMode.AlwaysMix; + layerMixModes[layer] = mode; + } + return mode; + } + else { + return layerBlendMode == MixBlend.Add ? MixMode.AlwaysMix : MixMode.MixNext; + } + } + +#if UNITY_EDITOR void GetLayerBlendModes() { if (layerBlendModes.Length < animator.layerCount) { System.Array.Resize(ref layerBlendModes, animator.layerCount); diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.cs index e3b88ccfc..673bd036f 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.cs @@ -191,8 +191,10 @@ namespace Spine.Unity { skeletonRenderer.LateUpdate(); - foreach (var s in partsRenderers) - s.ClearMesh(); + foreach (var partsRenderer in partsRenderers) { + if (partsRenderer != null) + partsRenderer.ClearMesh(); + } } MaterialPropertyBlock copiedBlock; @@ -221,6 +223,8 @@ namespace Spine.Unity { int rendererIndex = 0; var currentRenderer = partsRenderers[rendererIndex]; for (int si = 0, start = 0; si <= lastSubmeshInstruction; si++) { + if (currentRenderer == null) + continue; if (submeshInstructionsItems[si].forceSeparate || si == lastSubmeshInstruction) { // Apply properties var meshGenerator = currentRenderer.MeshGenerator; @@ -245,7 +249,9 @@ namespace Spine.Unity { // Clear extra renderers if they exist. for (; rendererIndex < rendererCount; rendererIndex++) { - partsRenderers[rendererIndex].ClearMesh(); + currentRenderer = partsRenderers[rendererIndex]; + if (currentRenderer != null) + partsRenderers[rendererIndex].ClearMesh(); } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs index 3e98eef8f..e75c00682 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs @@ -529,7 +529,7 @@ namespace Spine.Unity { separatorSlots.Add(slot); } #if UNITY_EDITOR - else + else if (!string.IsNullOrEmpty(separatorSlotNames[i])) { Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonDataAsset.skeletonJSON.name); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs index 153f94e6f..87da59f34 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs @@ -503,7 +503,10 @@ namespace Spine.Unity { for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) { var slot = drawOrderItems[slotIndex]; - if (!slot.bone.active) continue; + if (!slot.bone.active) { + clipper.ClipEnd(slot); + continue; + } var attachment = slot.attachment; float z = zSpacing * slotIndex; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesPixelLit-Outline.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesPixelLit-Outline.shader index 1cb3e0175..844a990e4 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesPixelLit-Outline.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesPixelLit-Outline.shader @@ -11,8 +11,6 @@ Shader "Spine/Outline/Sprite/Pixel Lit" _BumpMap ("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesUnlit-Outline.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesUnlit-Outline.shader index b687e0130..756f7da33 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesUnlit-Outline.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesUnlit-Outline.shader @@ -8,8 +8,6 @@ Shader "Spine/Outline/Sprite/Unlit" _Color ("Color", Color) = (1,1,1,1) [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _ZWrite ("Depth Write", Float) = 0.0 _Cutoff ("Depth alpha cutoff", Range(0,1)) = 0.0 diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesVertexLit-Outline.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesVertexLit-Outline.shader index 3611d7956..6e087dd04 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesVertexLit-Outline.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesVertexLit-Outline.shader @@ -11,8 +11,6 @@ Shader "Spine/Outline/Sprite/Vertex Lit" _BumpMap ("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/CGIncludes/ShaderShared.cginc b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/CGIncludes/ShaderShared.cginc index c8918952e..34f8c0b47 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/CGIncludes/ShaderShared.cginc +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/CGIncludes/ShaderShared.cginc @@ -1,7 +1,5 @@ // Upgrade NOTE: upgraded instancing buffer 'PerDrawSprite' to new syntax. -// Upgrade NOTE: upgraded instancing buffer 'PerDrawSprite' to new syntax. - #ifndef SHADER_SHARED_INCLUDED #define SHADER_SHARED_INCLUDED @@ -13,28 +11,6 @@ #include "UnityCG.cginc" #endif -#ifdef UNITY_INSTANCING_ENABLED - - UNITY_INSTANCING_BUFFER_START(PerDrawSprite) - // SpriteRenderer.Color while Non-Batched/Instanced. - fixed4 unity_SpriteRendererColorArray[UNITY_INSTANCED_ARRAY_SIZE]; - // this could be smaller but that's how bit each entry is regardless of type - float4 unity_SpriteFlipArray[UNITY_INSTANCED_ARRAY_SIZE]; - UNITY_INSTANCING_BUFFER_END(PerDrawSprite) - - #define _RendererColor unity_SpriteRendererColorArray[unity_InstanceID] - #define _Flip unity_SpriteFlipArray[unity_InstanceID] - -#endif // instancing - -CBUFFER_START(UnityPerDrawSprite) -#ifndef UNITY_INSTANCING_ENABLED - fixed4 _RendererColor; - float4 _Flip; -#endif - float _EnableExternalAlpha; -CBUFFER_END - //////////////////////////////////////// // Space functions // @@ -376,11 +352,6 @@ inline fixed4 applyFog(fixed4 pixel, float fogCoordOrFactorAtLWRP) uniform sampler2D _MainTex; -#if ETC1_EXTERNAL_ALPHA -//External alpha texture for ETC1 compression -uniform sampler2D _AlphaTex; -#endif //ETC1_EXTERNAL_ALPHA - #if _TEXTURE_BLEND uniform sampler2D _BlendTex; uniform float _BlendAmount; @@ -401,11 +372,6 @@ inline fixed4 calculateTexturePixel(float2 texcoord) pixel = tex2D(_MainTex, texcoord); #endif // !_TEXTURE_BLEND -#if ETC1_EXTERNAL_ALPHA - fixed4 alpha = tex2D (_AlphaTex, texcoord); - pixel.a = lerp (pixel.a, alpha.r, _EnableExternalAlpha); -#endif - #if defined(_COLOR_ADJUST) pixel = adjustColor(pixel); #endif // _COLOR_ADJUST diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesPixelLit.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesPixelLit.shader index acd4e5e33..7bdd1ab02 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesPixelLit.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesPixelLit.shader @@ -9,8 +9,6 @@ Shader "Spine/Sprite/Pixel Lit" _BumpMap ("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesUnlit.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesUnlit.shader index 66c166055..39ae8d994 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesUnlit.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesUnlit.shader @@ -6,8 +6,6 @@ Shader "Spine/Sprite/Unlit" _Color ("Color", Color) = (1,1,1,1) [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _ZWrite ("Depth Write", Float) = 0.0 _Cutoff ("Depth alpha cutoff", Range(0,1)) = 0.0 diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesVertexLit.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesVertexLit.shader index b58ad42a5..366e75aef 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesVertexLit.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesVertexLit.shader @@ -9,8 +9,6 @@ Shader "Spine/Sprite/Vertex Lit" _BumpMap ("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} 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 2ccb7e52d..5aa3f7db3 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs @@ -27,10 +27,16 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#if UNITY_2019_3_OR_NEWER +#define CONFIGURABLE_ENTER_PLAY_MODE +#endif + + using UnityEngine; using System.Collections.Generic; using System; + namespace Spine.Unity.AttachmentTools { public static class AtlasUtilities { @@ -41,6 +47,14 @@ namespace Spine.Unity.AttachmentTools { const int NonrenderingRegion = -1; + #if CONFIGURABLE_ENTER_PLAY_MODE + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void Init () { + // handle disabled domain reload + AtlasUtilities.ClearCache(); + } + #endif + public static AtlasRegion ToAtlasRegion (this Texture2D t, Material materialPropertySource, float scale = DefaultScale) { return t.ToAtlasRegion(materialPropertySource.shader, scale, materialPropertySource); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs index cbca589aa..48020800e 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs @@ -442,7 +442,7 @@ namespace Spine { } } - public static class SkeletonExtensions { + public static class SpineSkeletonExtensions { public static bool IsWeighted (this VertexAttachment va) { return va.bones != null && va.bones.Length > 0; } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs new file mode 100644 index 000000000..03f14aa55 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs @@ -0,0 +1,91 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + * + * THE SPINE RUNTIMES ARE 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using UnityEngine; +using System.Collections.Generic; +using System.Collections; + +namespace Spine.Unity.AnimationTools { + public static class TimelineExtensions { + + /// Evaluates the resulting value of a TranslateTimeline at a given time. + /// SkeletonData can be accessed from Skeleton.Data or from SkeletonDataAsset.GetSkeletonData. + /// If no SkeletonData is given, values are computed relative to setup pose instead of local-absolute. + public static Vector2 Evaluate (this TranslateTimeline timeline, float time, SkeletonData skeletonData = null) { + const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; + const int X = 1, Y = 2; + + var frames = timeline.frames; + if (time < frames[0]) return Vector2.zero; + + float x, y; + if (time >= frames[frames.Length - TranslateTimeline.ENTRIES]) { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, TranslateTimeline.ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = timeline.GetCurvePercent(frame / TranslateTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x += (frames[frame + X] - x) * percent; + y += (frames[frame + Y] - y) * percent; + } + + Vector2 xy = new Vector2(x, y); + if (skeletonData == null) { + return xy; + } + else { + var boneData = skeletonData.bones.Items[timeline.boneIndex]; + return xy + new Vector2(boneData.x, boneData.y); + } + } + + /// Gets the translate timeline for a given boneIndex. + /// You can get the boneIndex using SkeletonData.FindBoneIndex. + /// The root bone is always boneIndex 0. + /// This will return null if a TranslateTimeline is not found. + public static TranslateTimeline FindTranslateTimelineForBone (this Animation a, int boneIndex) { + foreach (var timeline in a.timelines) { + if (timeline.GetType().IsSubclassOf(typeof(TranslateTimeline))) + continue; + + var translateTimeline = timeline as TranslateTimeline; + if (translateTimeline != null && translateTimeline.boneIndex == boneIndex) + return translateTimeline; + } + return null; + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs.meta new file mode 100644 index 000000000..6198f84aa --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 07807fefbff25484ba41b1d16911fb0e +timeCreated: 1591974498 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.lwrp-shaders/Shaders/Spine-Sprite-LW.shader b/spine-unity/Modules/com.esotericsoftware.spine.lwrp-shaders/Shaders/Spine-Sprite-LW.shader index aca16c403..6c9bb44db 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.lwrp-shaders/Shaders/Spine-Sprite-LW.shader +++ b/spine-unity/Modules/com.esotericsoftware.spine.lwrp-shaders/Shaders/Spine-Sprite-LW.shader @@ -9,8 +9,6 @@ Shader "Lightweight Render Pipeline/Spine/Sprite" _BumpMap("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} diff --git a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-SkeletonLit-URP-2D.shader b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-SkeletonLit-URP-2D.shader index f129c2a77..7cf903eeb 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-SkeletonLit-URP-2D.shader +++ b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-SkeletonLit-URP-2D.shader @@ -119,6 +119,7 @@ Tags { "LightMode" = "NormalsRendering"} Blend SrcAlpha OneMinusSrcAlpha + ZWrite Off HLSLPROGRAM #pragma prefer_hlslcc gles diff --git a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader index 7c42f712d..d9f0ddbb9 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader +++ b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader @@ -10,8 +10,6 @@ Shader "Universal Render Pipeline/2D/Spine/Sprite" _BumpMap("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} @@ -116,6 +114,7 @@ Shader "Universal Render Pipeline/2D/Spine/Sprite" Blend SrcAlpha OneMinusSrcAlpha Cull[_Cull] + ZWrite[_ZWrite] HLSLPROGRAM #pragma prefer_hlslcc gles diff --git a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader index 79402b040..41c3f7aeb 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader +++ b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader @@ -9,8 +9,6 @@ Shader "Universal Render Pipeline/Spine/Sprite" _BumpMap("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {}