Ver código fonte

[unity] Fixed SkeletonRootMotion 3D rigidbody rotational root-motion. Closes #2143. Added `SkeletonRootMotion` callback delegates `ProcessRootMotionOverride` and `PhysicsUpdateRootMotionOverride` to customize how root motion is applied.

Harald Csaszar 3 anos atrás
pai
commit
87e5a84cff

+ 1 - 0
CHANGELOG.md

@@ -86,6 +86,7 @@
   * Added `UnscaledTime` property at `SkeletonAnimation` as well, behaving like `SkeletonGraphic.UnscaledTime`. If enabled, AnimationState uses unscaled game time (`Time.unscaledDeltaTime`), running animations independent of e.g. game pause (`Time.timeScale`).
   * `SkeletonAnimation`, `SkeletonMecanim` and `SkeletonGraphic` now provide an additional `OnAnimationRebuild` callback delegate which is issued after both the skeleton and the animation state have been initialized.
   * Timeline `SkeletonAnimation Track` and `SkeletonGraphic Track` now provide an `Unscaled Time` property. Whenever starting a new animation clip of this track, `SkeletonAnimation.UnscaledTime` or `SkeletonGraphic.UnscaledTime` will be set to this value. This allows you to play back Timeline clips either in normal game time or unscaled game time. Note that `PlayableDirector.UpdateMethod` is ignored and replaced by this property, which allows more fine-granular control per Timeline track.
+  * Added `SkeletonRootMotion` callback delegates `ProcessRootMotionOverride` and `PhysicsUpdateRootMotionOverride` to customize how root motion is applied. The new property `disableOnOverride` determines whether the callback will be issued in addition or instead of normally applying root motion. Added property `rootMotionScaleRotation` to allow scaling rotational root-motion to match e.g. a 90 degree rotation to a custom target angle.
  
 * **Breaking changes**
   * Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.

+ 5 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs

@@ -40,6 +40,7 @@ namespace Spine.Unity.Editor {
 		protected SerializedProperty transformRotation;
 		protected SerializedProperty rootMotionScaleX;
 		protected SerializedProperty rootMotionScaleY;
+		protected SerializedProperty rootMotionScaleRotation;
 		protected SerializedProperty rootMotionTranslateXPerY;
 		protected SerializedProperty rootMotionTranslateYPerX;
 		protected SerializedProperty rigidBody2D;
@@ -52,6 +53,7 @@ namespace Spine.Unity.Editor {
 		protected GUIContent transformRotationLabel;
 		protected GUIContent rootMotionScaleXLabel;
 		protected GUIContent rootMotionScaleYLabel;
+		protected GUIContent rootMotionScaleRotationLabel;
 		protected GUIContent rootMotionTranslateXPerYLabel;
 		protected GUIContent rootMotionTranslateYPerXLabel;
 		protected GUIContent rigidBody2DLabel;
@@ -66,6 +68,7 @@ namespace Spine.Unity.Editor {
 			transformRotation = serializedObject.FindProperty("transformRotation");
 			rootMotionScaleX = serializedObject.FindProperty("rootMotionScaleX");
 			rootMotionScaleY = serializedObject.FindProperty("rootMotionScaleY");
+			rootMotionScaleRotation = serializedObject.FindProperty("rootMotionScaleRotation");
 			rootMotionTranslateXPerY = serializedObject.FindProperty("rootMotionTranslateXPerY");
 			rootMotionTranslateYPerX = serializedObject.FindProperty("rootMotionTranslateYPerX");
 			rigidBody2D = serializedObject.FindProperty("rigidBody2D");
@@ -78,6 +81,7 @@ namespace Spine.Unity.Editor {
 			transformRotationLabel = new UnityEngine.GUIContent("Rotation", "Use the rotation of the bone.");
 			rootMotionScaleXLabel = new UnityEngine.GUIContent("Root Motion Scale (X)", "Scale applied to the horizontal root motion delta. Can be used for delta compensation to e.g. stretch a jump to the desired distance.");
 			rootMotionScaleYLabel = new UnityEngine.GUIContent("Root Motion Scale (Y)", "Scale applied to the vertical root motion delta. Can be used for delta compensation to e.g. stretch a jump to the desired distance.");
+			rootMotionScaleRotationLabel = new UnityEngine.GUIContent("Root Motion Scale (Rotation)", "Scale applied to the rotational root motion delta. Can be used for delta compensation to e.g. adjust an angled jump landing to the desired platform angle.");
 			rootMotionTranslateXPerYLabel = new UnityEngine.GUIContent("Root Motion Translate (X)", "Added X translation per root motion Y delta. Can be used for delta compensation when scaling is not enough, to e.g. offset a horizontal jump to a vertically different goal.");
 			rootMotionTranslateYPerXLabel = new UnityEngine.GUIContent("Root Motion Translate (Y)", "Added Y translation per root motion X delta. Can be used for delta compensation when scaling is not enough, to e.g. offset a horizontal jump to a vertically different goal.");
 			rigidBody2DLabel = new UnityEngine.GUIContent("Rigidbody2D",
@@ -110,6 +114,7 @@ namespace Spine.Unity.Editor {
 
 			EditorGUILayout.PropertyField(rootMotionScaleX, rootMotionScaleXLabel);
 			EditorGUILayout.PropertyField(rootMotionScaleY, rootMotionScaleYLabel);
+			EditorGUILayout.PropertyField(rootMotionScaleRotation, rootMotionScaleRotationLabel);
 
 			EditorGUILayout.PropertyField(rootMotionTranslateXPerY, rootMotionTranslateXPerYLabel);
 			EditorGUILayout.PropertyField(rootMotionTranslateYPerX, rootMotionTranslateYPerXLabel);

+ 90 - 43
spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs

@@ -50,6 +50,7 @@ namespace Spine.Unity {
 
 		public float rootMotionScaleX = 1;
 		public float rootMotionScaleY = 1;
+		public float rootMotionScaleRotation = 1;
 		/// <summary>Skeleton space X translation per skeleton space Y translation root motion.</summary>
 		public float rootMotionTranslateXPerY = 0;
 		/// <summary>Skeleton space Y translation per skeleton space X translation root motion.</summary>
@@ -60,6 +61,36 @@ namespace Spine.Unity {
 		public bool applyRigidbody2DGravity = false;
 		public Rigidbody rigidBody;
 
+		/// <summary>Delegate type for customizing application of rootmotion.
+		public delegate void RootMotionDelegate (SkeletonRootMotionBase component, Vector2 translation, float rotation);
+		/// <summary>This callback can be used to apply root-motion in a custom way. It is raised after evaluating
+		/// this animation frame's root-motion, before it is potentially applied (see <see cref="disableOnOverride"/>)
+		/// to either Transform or Rigidbody.
+		/// When <see cref="SkeletonAnimation.UpdateTiming"/> is set to <see cref="UpdateTiming.InUpdate"/>, multiple
+		/// animation frames might take place before <c>FixedUpdate</c> is called once.
+		/// The callback parameters <c>translation</c> and <c>rotation</c> are filled out with
+		/// this animation frame's skeleton-space root-motion (not cumulated). You can use
+		/// e.g. <c>transform.TransformVector()</c> to transform skeleton-space root-motion to world space.
+		/// </summary>
+		/// <seealso cref="PhysicsUpdateRootMotionOverride"/>
+		public event RootMotionDelegate ProcessRootMotionOverride;
+		/// <summary>This callback can be used to apply root-motion in a custom way. It is raised in FixedUpdate
+		/// after (when <see cref="disableOnOverride"/> is set to false) or instead of when root-motion
+		/// would be applied at the Rigidbody.
+		/// When <see cref="SkeletonAnimation.UpdateTiming"/> is set to <see cref="UpdateTiming.InUpdate"/>, multiple
+		/// animation frames might take place before before <c>FixedUpdate</c> is called once.
+		/// The callback parameters <c>translation</c> and <c>rotation</c> are filled out with the
+		/// (cumulated) skeleton-space root-motion since the the last <c>FixedUpdate</c> call. You can use
+		/// e.g. <c>transform.TransformVector()</c> to transform skeleton-space root-motion to world space.
+		/// </summary>
+		/// <seealso cref="ProcessRootMotionOverride"/>
+		public event RootMotionDelegate PhysicsUpdateRootMotionOverride;
+		/// <summary>When true, root-motion is not applied to the Transform or Rigidbody.
+		/// Otherwise the delegate callbacks are issued additionally.</summary>
+		public bool disableOnOverride = true;
+
+		public Bone RootMotionBone { get { return rootMotionBone; } }
+
 		public bool UsesRigidbody {
 			get { return rigidBody != null || rigidBody2D != null; }
 		}
@@ -108,12 +139,13 @@ namespace Spine.Unity {
 		protected List<float> transformConstraintLastRotation = new List<float>();
 		protected List<Bone> topLevelBones = new List<Bone>();
 		protected Vector2 initialOffset = Vector2.zero;
+		protected bool accumulatedUntilFixedUpdate = false;
 		protected Vector2 tempSkeletonDisplacement;
 		protected Vector3 rigidbodyDisplacement;
 		protected Vector3 previousRigidbodyRootMotion = Vector2.zero;
 		protected Vector2 additionalRigidbody2DMovement = Vector2.zero;
 
-		protected Quaternion rigidbodyRotation = Quaternion.identity;
+		protected Quaternion rigidbodyLocalRotation = Quaternion.identity;
 		protected float rigidbody2DRotation;
 		protected float initialOffsetRotation;
 		protected float tempSkeletonRotation;
@@ -149,35 +181,44 @@ namespace Spine.Unity {
 		}
 
 		protected virtual void PhysicsUpdate (bool skeletonAnimationUsesFixedUpdate) {
-			if (rigidBody2D != null) {
-				Vector2 gravityAndVelocityMovement = Vector2.zero;
-				if (applyRigidbody2DGravity) {
-					float deltaTime = Time.fixedDeltaTime;
-					float deltaTimeSquared = (deltaTime * deltaTime);
-
-					rigidBody2D.velocity += rigidBody2D.gravityScale * Physics2D.gravity * deltaTime;
-					gravityAndVelocityMovement = 0.5f * rigidBody2D.gravityScale * Physics2D.gravity * deltaTimeSquared +
-						rigidBody2D.velocity * deltaTime;
-				}
+			Vector2 callbackDisplacement = tempSkeletonDisplacement;
+			float callbackRotation = tempSkeletonRotation;
+
+			bool isApplyAtRigidbodyAllowed = PhysicsUpdateRootMotionOverride == null || !disableOnOverride;
+			if (isApplyAtRigidbodyAllowed) {
+				if (rigidBody2D != null) {
+					Vector2 gravityAndVelocityMovement = Vector2.zero;
+					if (applyRigidbody2DGravity) {
+						float deltaTime = Time.fixedDeltaTime;
+						float deltaTimeSquared = (deltaTime * deltaTime);
+
+						rigidBody2D.velocity += rigidBody2D.gravityScale * Physics2D.gravity * deltaTime;
+						gravityAndVelocityMovement = 0.5f * rigidBody2D.gravityScale * Physics2D.gravity * deltaTimeSquared +
+							rigidBody2D.velocity * deltaTime;
+					}
 
-				Vector2 rigidbodyDisplacement2D = new Vector2(rigidbodyDisplacement.x, rigidbodyDisplacement.y);
-				rigidBody2D.MovePosition(gravityAndVelocityMovement + new Vector2(transform.position.x, transform.position.y)
-					+ rigidbodyDisplacement2D + additionalRigidbody2DMovement);
-				rigidBody2D.MoveRotation(rigidbody2DRotation + rigidBody2D.rotation);
-			} else if (rigidBody != null) {
-				rigidBody.MovePosition(transform.position
-					+ new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0));
-				rigidBody.MoveRotation(rigidBody.rotation * rigidbodyRotation);
-			} else return;
+					Vector2 rigidbodyDisplacement2D = new Vector2(rigidbodyDisplacement.x, rigidbodyDisplacement.y);
+					rigidBody2D.MovePosition(gravityAndVelocityMovement + new Vector2(transform.position.x, transform.position.y)
+						+ rigidbodyDisplacement2D + additionalRigidbody2DMovement);
+					rigidBody2D.MoveRotation(rigidbody2DRotation + rigidBody2D.rotation);
+				} else if (rigidBody != null) {
+					rigidBody.MovePosition(transform.position
+						+ new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, rigidbodyDisplacement.z));
+					rigidBody.MoveRotation(rigidBody.rotation * rigidbodyLocalRotation);
+				}
+			}
 
-			Vector2 parentBoneScale;
-			GetScaleAffectingRootMotion(out parentBoneScale);
-			if (!skeletonAnimationUsesFixedUpdate) {
+			previousRigidbodyRootMotion = rigidbodyDisplacement;
+			if (accumulatedUntilFixedUpdate) {
+				Vector2 parentBoneScale;
+				GetScaleAffectingRootMotion(out parentBoneScale);
 				ClearEffectiveBoneOffsets(parentBoneScale);
 				skeletonComponent.Skeleton.UpdateWorldTransform();
 			}
-			previousRigidbodyRootMotion = rigidbodyDisplacement;
 			ClearRigidbodyTempMovement();
+
+			if (PhysicsUpdateRootMotionOverride != null)
+				PhysicsUpdateRootMotionOverride(this, callbackDisplacement, callbackRotation);
 		}
 
 		protected virtual void OnDisable () {
@@ -499,6 +540,7 @@ namespace Spine.Unity {
 			float skeletonRotationDelta = 0;
 			if (transformRotation) {
 				float boneLocalDeltaRotation = CalculateAnimationsRotationDelta();
+				boneLocalDeltaRotation *= rootMotionScaleRotation;
 				skeletonRotationDelta = GetSkeletonSpaceRotationDelta(boneLocalDeltaRotation, totalScale);
 			}
 
@@ -511,40 +553,45 @@ namespace Spine.Unity {
 
 		void ApplyRootMotion (Vector2 skeletonTranslationDelta, float skeletonRotationDelta, Vector2 parentBoneScale,
 			bool skeletonAnimationUsesFixedUpdate) {
-			// Apply root motion to Transform or RigidBody;
-			if (UsesRigidbody) {
-				rigidbodyDisplacement += transform.TransformVector(skeletonTranslationDelta);
 
-				// Accumulated displacement is applied on the next Physics update in FixedUpdate.
-				// Until the next Physics update, tempBoneDisplacement is offsetting bone locations
-				// to prevent stutter which would otherwise occur if we don't move every Update.
-				if (!skeletonAnimationUsesFixedUpdate)
-					tempSkeletonDisplacement += skeletonTranslationDelta;
+			// Accumulated displacement is applied on the next Physics update in FixedUpdate.
+			// Until the next Physics update, tempSkeletonDisplacement and tempSkeletonRotation
+			// are offsetting bone locations to prevent stutter which would otherwise occur if
+			// we don't move every Update.
+			bool usesRigidbody = this.UsesRigidbody;
+			bool applyToTransform = !usesRigidbody && (ProcessRootMotionOverride == null || !disableOnOverride);
+			accumulatedUntilFixedUpdate = !applyToTransform && !skeletonAnimationUsesFixedUpdate;
+
+			if (ProcessRootMotionOverride != null)
+				ProcessRootMotionOverride(this, skeletonTranslationDelta, skeletonRotationDelta);
 
+			// Apply root motion to Transform or update values applied to RigidBody later (must happen in FixedUpdate).
+			if (usesRigidbody) {
+				rigidbodyDisplacement += transform.TransformVector(skeletonTranslationDelta);
 				if (skeletonRotationDelta != 0.0f) {
 					if (rigidBody != null) {
-						Quaternion addedWorldRotation = Quaternion.AngleAxis(skeletonRotationDelta, transform.forward);
-						rigidbodyRotation = rigidbodyRotation * addedWorldRotation;
+						Quaternion addedWorldRotation = Quaternion.Euler(0, 0, skeletonRotationDelta);
+						rigidbodyLocalRotation = rigidbodyLocalRotation * addedWorldRotation;
 					} else if (rigidBody2D != null) {
 						Vector3 lossyScale = transform.lossyScale;
 						float rotationSign = lossyScale.x * lossyScale.y > 0 ? 1 : -1;
 						rigidbody2DRotation += rotationSign * skeletonRotationDelta;
 					}
-					if (!skeletonAnimationUsesFixedUpdate)
-						tempSkeletonRotation += skeletonRotationDelta;
 				}
-
-				if (skeletonAnimationUsesFixedUpdate)
-					ClearEffectiveBoneOffsets(parentBoneScale);
-				else
-					SetEffectiveBoneOffsetsTo(tempSkeletonDisplacement, tempSkeletonRotation, parentBoneScale);
-			} else {
+			} else if (applyToTransform) {
 				transform.position += transform.TransformVector(skeletonTranslationDelta);
 				if (skeletonRotationDelta != 0.0f) {
 					Vector3 lossyScale = transform.lossyScale;
 					float rotationSign = lossyScale.x * lossyScale.y > 0 ? 1 : -1;
 					transform.Rotate(0, 0, rotationSign * skeletonRotationDelta);
 				}
+			}
+
+			tempSkeletonDisplacement += skeletonTranslationDelta;
+			tempSkeletonRotation += skeletonRotationDelta;
+			if (accumulatedUntilFixedUpdate) {
+				SetEffectiveBoneOffsetsTo(tempSkeletonDisplacement, tempSkeletonRotation, parentBoneScale);
+			} else {
 				ClearEffectiveBoneOffsets(parentBoneScale);
 			}
 		}
@@ -654,7 +701,7 @@ namespace Spine.Unity {
 		void ClearRigidbodyTempMovement () {
 			rigidbodyDisplacement = Vector2.zero;
 			tempSkeletonDisplacement = Vector2.zero;
-			rigidbodyRotation = Quaternion.identity;
+			rigidbodyLocalRotation = Quaternion.identity;
 			rigidbody2DRotation = 0;
 			tempSkeletonRotation = 0;
 		}