mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-06 07:14:55 +08:00
[unity] Added Root Motion support for SkeletonAnimation, SkeletonMecanim and SkeletonGraphic. See #1417.
This commit is contained in:
parent
f8574f1392
commit
528ab0cff2
@ -231,6 +231,7 @@
|
||||
* 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.
|
||||
|
||||
* **Changes of default values**
|
||||
* `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`.
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -234,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);
|
||||
|
||||
|
||||
@ -30,21 +30,119 @@
|
||||
// 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 layerMixModes;
|
||||
protected SerializedProperty layerBlendModes;
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
mecanimTranslator = serializedObject.FindProperty("translator");
|
||||
SerializedProperty mecanimTranslator = serializedObject.FindProperty("translator");
|
||||
autoReset = mecanimTranslator.FindPropertyRelative("autoReset");
|
||||
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.Space();
|
||||
DrawLayerSettings();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddRootMotionComponentIfEnabled () {
|
||||
foreach (var t in targets) {
|
||||
var component = t as Component;
|
||||
var animator = component.GetComponent<Animator>();
|
||||
if (animator != null && animator.applyRootMotion) {
|
||||
if (component.GetComponent<SkeletonMecanimRootMotion>() == null) {
|
||||
component.gameObject.AddComponent<SkeletonMecanimRootMotion>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4613924c50d66cf458f0db803776dd2f
|
||||
timeCreated: 1593175106
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -106,7 +106,6 @@ namespace Spine.Unity.Editor {
|
||||
#else
|
||||
isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
|
||||
#endif
|
||||
|
||||
SpineEditorUtilities.ConfirmInitialization();
|
||||
|
||||
// Labels
|
||||
@ -398,6 +397,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<SkeletonRootMotion>() != 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<SkeletonRootMotion>() == null) {
|
||||
component.gameObject.AddComponent<SkeletonRootMotion>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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<SkeletonRootMotion>();
|
||||
if (rootMotionComponent != null) {
|
||||
DestroyImmediate(rootMotionComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetSeparatorSlotNames (SkeletonRenderer skeletonRenderer, string[] newSlotNames) {
|
||||
var field = SpineInspectorUtility.GetNonPublicField(typeof(SkeletonRenderer), SeparatorSlotNamesFieldName);
|
||||
field.SetValue(skeletonRenderer, newSlotNames);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2cba83baf6afdf44a996e40017c6325
|
||||
timeCreated: 1593175106
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4836100aed984c4a9af11d39c63cb6b
|
||||
timeCreated: 1593183609
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b957aa69dae9f948bacdeec549d28ea
|
||||
folderAsset: yes
|
||||
timeCreated: 1593173800
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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 {
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only compatible with <c>SkeletonMecanim</c>.
|
||||
/// For <c>SkeletonAnimation</c> or <c>SkeletonGraphic</c> please use
|
||||
/// <see cref="SkeletonRootMotion">SkeletonRootMotion</see> instead.
|
||||
/// </remarks>
|
||||
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<SkeletonMecanim>();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
mecanimLayerFlags = DefaultMecanimLayerFlags;
|
||||
}
|
||||
|
||||
protected override void Start () {
|
||||
base.Start();
|
||||
skeletonMecanim = GetComponent<SkeletonMecanim>();
|
||||
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<<layerIndex) == 0) || weight == 0)
|
||||
return;
|
||||
|
||||
var timeline = clip.FindTranslateTimelineForBone(rootMotionBoneIndex);
|
||||
if (timeline != null) {
|
||||
if (!playsBackward)
|
||||
movementDelta += weight * GetTimelineMovementDelta(lastTime, time, timeline, clip);
|
||||
else
|
||||
movementDelta -= weight * GetTimelineMovementDelta(time, lastTime, timeline, clip);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateAnimationsMovementDelta () {
|
||||
// Note: movement delta is not gather after animation but
|
||||
// in OnClipApplied after every applied animation.
|
||||
Vector2 result = movementDelta;
|
||||
movementDelta = Vector2.zero;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95813afe390494344a6ce2cbc8bfb7d1
|
||||
timeCreated: 1592849332
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,143 @@
|
||||
/******************************************************************************
|
||||
* 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 {
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only compatible with SkeletonAnimation (or other components that implement
|
||||
/// ISkeletonComponent, ISkeletonAnimation and IAnimationStateComponent).
|
||||
/// For <c>SkeletonMecanim</c> please use
|
||||
/// <see cref="SkeletonMecanimRootMotion">SkeletonMecanimRootMotion</see> instead.
|
||||
/// </remarks>
|
||||
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<CanvasRenderer>() != null) {
|
||||
canvas = this.GetComponentInParent<Canvas>();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f21c9538588898a45a3da22bf4779ab3
|
||||
timeCreated: 1591121072
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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 {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for skeleton root motion components.
|
||||
/// </summary>
|
||||
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<Bone> topLevelBones = new List<Bone>();
|
||||
protected Vector2 rigidbodyDisplacement;
|
||||
|
||||
protected virtual void Reset () {
|
||||
FindRigidbodyComponent();
|
||||
}
|
||||
|
||||
protected virtual void Start () {
|
||||
skeletonComponent = GetComponent<ISkeletonComponent>();
|
||||
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<Rigidbody2D>();
|
||||
if (!rigidBody2D)
|
||||
rigidBody = this.GetComponent<Rigidbody>();
|
||||
|
||||
if (!rigidBody2D && !rigidBody) {
|
||||
rigidBody2D = this.GetComponentInParent<Rigidbody2D>();
|
||||
if (!rigidBody2D)
|
||||
rigidBody = this.GetComponentInParent<Rigidbody>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc23a4f220b20024ab0592719f92587d
|
||||
timeCreated: 1592849332
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -129,6 +129,12 @@ namespace Spine.Unity {
|
||||
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<int, Spine.Animation> animationTable = new Dictionary<int, Spine.Animation>(IntEqualityComparer.Instance);
|
||||
@ -157,6 +163,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,10 +197,71 @@ namespace Spine.Unity {
|
||||
ClearClipInfosForLayers();
|
||||
}
|
||||
|
||||
private bool ApplyAnimation (Skeleton skeleton, AnimatorClipInfo info, AnimatorStateInfo stateInfo,
|
||||
int layerIndex, float layerWeight, MixBlend layerBlendMode, bool useWeight1 = 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 = useWeight1 ? 1.0f : 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 useWeight1 = 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 = useWeight1 ? 1.0f : 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 (layerMixModes.Length < animator.layerCount) {
|
||||
int oldSize = layerMixModes.Length;
|
||||
System.Array.Resize<MixMode>(ref layerMixModes, animator.layerCount);
|
||||
layerMixModes[animator.layerCount-1] = MixMode.MixNext;
|
||||
for (int layer = oldSize; layer < animator.layerCount; ++layer) {
|
||||
layerMixModes[layer] = layer == 0 ? MixMode.MixNext : MixMode.AlwaysMix;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
@ -257,56 +344,41 @@ namespace Spine.Unity {
|
||||
|
||||
int clipInfoCount, nextClipInfoCount, interruptingClipInfoCount;
|
||||
IList<AnimatorClipInfo> 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;
|
||||
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, useWeight1: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 +386,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, useWeight1: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 +403,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, useWeight1: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 +423,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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user