Before 优化 机场

This commit is contained in:
CortexCore
2025-03-10 18:06:44 +08:00
parent 350e6d67b2
commit 1f4e20f512
178 changed files with 17534 additions and 821 deletions

View File

@@ -0,0 +1,427 @@
// Designed by KINEMATION, 2024.
using KINEMATION.MotionWarping.Runtime.Core;
using Kinemation.MotionWarping.Runtime.Utility;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using Debug = UnityEngine.Debug;
using Quaternion = UnityEngine.Quaternion;
using Vector3 = UnityEngine.Vector3;
namespace Kinemation.MotionWarping.Runtime.Core
{
public class MotionWarping : MonoBehaviour
{
[Header("Warping")]
[SerializeField] private bool scalePlayRate = true;
[Header("Animator")]
[SerializeField, Tooltip("Will try to play the animation.")] private bool playAnimator;
[SerializeField, Range(0f, 1f)] private float blendTime;
[Header("Events")]
public UnityEvent onWarpStarted;
public UnityEvent onWarpEnded;
[SerializeField]
private Animator animator;
public WarpPhase[] warpPhases;
private Vector3 _endCurveValue;
private Vector3 _startCurveValue;
private int _phaseIndex;
private MotionWarpingAsset _asset;
private WarpPhase _warpPhase;
private float _nextPhaseTime;
private Vector3 _originPosition;
private Quaternion _originRotation;
private bool _bUpdateWarping;
private float _warpPlayback;
private float _rateScale = 1f;
private float _warpLength;
private Vector3 _accumRootMotion;
private Vector3 _rootMotion;
private bool _hasActivePhase;
private static readonly int WarpRate = Animator.StringToHash("WarpRate");
private MotionWarpingIk _motionWarpingIk;
private void Start()
{
animator = GetComponentInChildren<Animator>();
_motionWarpingIk = GetComponent<MotionWarpingIk>();
}
private void OnDestroy()
{
onWarpEnded = onWarpStarted = null;
}
private float InvLerp(float startValue, float targetValue, float curveValue)
{
if (Mathf.Approximately(startValue, targetValue)) return 0f;
float numerator = curveValue - startValue;
float denominator = targetValue - startValue;
return Mathf.Approximately(denominator, 0f) ? 0f : numerator / denominator;
}
private float SafeDivide(float a, float b)
{
if (Mathf.Approximately(b, 0f)) return 0f;
return a / b;
}
private float GetNormalizedPlayback()
{
return _warpPlayback / _warpLength;
}
private float GetPhaseProgress()
{
float alpha = InvLerp(_warpPhase.startTime, _warpPhase.endTime, _warpPlayback);
return Mathf.Clamp01(alpha);
}
private void EnterNewPhase()
{
_originPosition = transform.position;
_originRotation = transform.rotation;
_accumRootMotion = _rootMotion = Vector3.zero;
_warpPhase = warpPhases[_phaseIndex];
_nextPhaseTime = _phaseIndex == warpPhases.Length - 1 ? _warpLength : warpPhases[_phaseIndex + 1].startTime;
_hasActivePhase = true;
_startCurveValue = _asset.GetVectorValue(_warpPhase.startTime);
_endCurveValue = _asset.GetVectorValue(_warpPhase.endTime);
_phaseIndex++;
if (scalePlayRate && animator != null)
{
float curveVec = (_endCurveValue - _startCurveValue).magnitude;
float realVec = (_warpPhase.Target.GetPosition() - _originPosition).magnitude;
realVec = Mathf.Max(0.001f, realVec);
_rateScale = Mathf.Clamp(curveVec / realVec, _warpPhase.minRate, _warpPhase.maxRate);
_rateScale *= _asset.playRateBasis;
animator.SetFloat(WarpRate, _rateScale);
}
else
{
_rateScale = 1f;
}
if (_warpPhase.Target.transform != null)
{
_originPosition = _warpPhase.Target.transform.InverseTransformPoint(transform.position);
_originRotation = Quaternion.Inverse(_warpPhase.Target.transform.rotation) * transform.rotation;
}
}
private void ExitCurrentPhase()
{
_hasActivePhase = false;
if (_warpPhase.Target.transform == null)
{
_originPosition = transform.position;
_originRotation = transform.rotation;
}
else
{
_originPosition = _warpPhase.Target.transform.InverseTransformPoint(transform.position);
_originRotation = Quaternion.Inverse(_warpPhase.Target.transform.rotation) * transform.rotation;
}
_startCurveValue = _endCurveValue = _asset.GetVectorValue(_warpPlayback);
}
private Quaternion WarpRotation()
{
float alpha = _hasActivePhase ? GetPhaseProgress() : 0f;
return Quaternion.Slerp(transform.rotation, _warpPhase.Target.GetRotation(), alpha);
}
private Vector3 WarpTranslation()
{
// 1. Compute the original additive curve value
Vector3 prevRootMotion = _rootMotion;
_rootMotion = _asset.GetVectorValue(_warpPlayback) - _startCurveValue;
if (!_hasActivePhase)
{
// 2. If not in the segment - play the animation itself.
Vector3 modifiedRootMotion = _rootMotion;
modifiedRootMotion.x = _asset.useAnimation.x ? modifiedRootMotion.x : 0f;
modifiedRootMotion.y = _asset.useAnimation.y ? modifiedRootMotion.y : 0f;
modifiedRootMotion.z = _asset.useAnimation.z ? modifiedRootMotion.z : 0f;
return modifiedRootMotion;
}
// 3. Compute the target in the origin space
Vector3 localTarget = transform.InverseTransformPoint(_warpPhase.Target.GetPosition());
// 4. Compute the deltas.
Vector3 animationTarget = _endCurveValue - _startCurveValue;
animationTarget.x = _asset.useAnimation.x ? animationTarget.x : 0f;
animationTarget.y = _asset.useAnimation.y ? animationTarget.y : 0f;
animationTarget.z = _asset.useAnimation.z ? animationTarget.z : 0f;
Vector3 targetDelta = localTarget - animationTarget;
Vector3 rootMotionDelta = _rootMotion - prevRootMotion;
_accumRootMotion.x += Mathf.Abs(rootMotionDelta.x);
_accumRootMotion.y += Mathf.Abs(rootMotionDelta.y);
_accumRootMotion.z += Mathf.Abs(rootMotionDelta.z);
// 5. Finally warp the motion.
targetDelta.x *= _asset.useLinear.x
? GetPhaseProgress()
: Mathf.Clamp01(SafeDivide(_accumRootMotion.x, _warpPhase.totalRootMotion.x));
targetDelta.y *= _asset.useLinear.y
? GetPhaseProgress()
: Mathf.Clamp01(SafeDivide(_accumRootMotion.y, _warpPhase.totalRootMotion.y));
targetDelta.z *= _asset.useLinear.z
? GetPhaseProgress()
: Mathf.Clamp01(SafeDivide(_accumRootMotion.z, _warpPhase.totalRootMotion.z));
Vector3 rootAnimation = Vector3.zero;
rootAnimation.x = _asset.useAnimation.x ? _rootMotion.x : 0f;
rootAnimation.y = _asset.useAnimation.y ? _rootMotion.y : 0f;
rootAnimation.z = _asset.useAnimation.z ? _rootMotion.z : 0f;
return rootAnimation + targetDelta;
}
private void WarpAnimation()
{
Vector3 cachedPosition = transform.position;
if (_warpPhase.Target.transform == null)
{
transform.position = _originPosition;
transform.rotation = _originRotation;
}
else
{
transform.position = _warpPhase.Target.transform.TransformPoint(_originPosition);
transform.rotation = _warpPhase.Target.transform.rotation * _originRotation;
}
Vector3 warpedTranslation = WarpTranslation();
Quaternion warpedRotation = WarpRotation();
warpedTranslation = transform.TransformPoint(warpedTranslation);
if (_asset.useCollision)
{
}
else
{
transform.position = warpedTranslation;
}
transform.rotation = warpedRotation;
}
private void UpdateWarping()
{
if (_warpPlayback > _warpPhase.endTime && _hasActivePhase)
{
ExitCurrentPhase();
}
if (!_hasActivePhase && _warpPlayback > _nextPhaseTime)
{
EnterNewPhase();
}
WarpAnimation();
// Update playback
_warpPlayback += Time.deltaTime * _rateScale;
_warpPlayback = Mathf.Clamp(_warpPlayback, 0f, _warpLength);
if (Mathf.Approximately(GetNormalizedPlayback(), 1f))
{
Stop();
}
}
private void LateUpdate()
{
if (!_bUpdateWarping) return;
UpdateWarping();
if (_motionWarpingIk == null) return;
_motionWarpingIk.ApplyIK();
}
private void Play_Internal(MotionWarpingAsset motionWarpingAsset)
{
if (playAnimator && animator != null)
{
animator.CrossFade(motionWarpingAsset.animation.name, blendTime);
}
_startCurveValue = _endCurveValue = _accumRootMotion = _rootMotion = Vector3.zero;
_warpPhase.Target.transform = null;
_originPosition = transform.position;
_originRotation = transform.rotation;
_asset = motionWarpingAsset;
_phaseIndex = 0;
_nextPhaseTime = warpPhases[0].startTime;
_bUpdateWarping = true;
_hasActivePhase = false;
_rateScale = 1f;
_warpLength = motionWarpingAsset.GetLength();
onWarpStarted.Invoke();
}
public bool Interact(GameObject target)
{
if (target == null)
{
return false;
}
return Interact(target.GetComponent<IWarpPointProvider>());
}
public bool Interact(IWarpPointProvider target)
{
if (target == null)
{
return false;
}
var result = target.Interact(gameObject);
if (!result.IsValid())
{
return false;
}
Play(result.asset, result.points);
return true;
}
public void Play(MotionWarpingAsset motionWarpingAsset, WarpPoint[] warpPoints)
{
if (motionWarpingAsset == null)
{
Debug.LogError("MotionWarping: WarpPoint[] warpPoints is null!");
return;
}
if (warpPoints == null)
{
Debug.LogError("MotionWarping: Warp Points array is null!");
return;
}
warpPhases = motionWarpingAsset.warpPhases.ToArray();
if (warpPhases.Length != warpPoints.Length)
{
Debug.LogError("MotionWarping: Warp Phases and Warp Points array do not match!");
return;
}
for (int i = 0; i < warpPhases.Length; i++)
{
WarpPhase phase = warpPhases[i];
WarpPoint target = warpPoints[i];
if (target.transform == null)
{
phase.Target.position = WarpingUtility.ToWorld(target.position, target.rotation,
phase.tOffset);
phase.Target.rotation = target.rotation * Quaternion.Euler(phase.rOffset);
}
else
{
phase.Target.transform = target.transform;
phase.Target.position = target.position;
phase.Target.rotation = target.rotation;
phase.Target.localPosition = phase.tOffset;
phase.Target.localRotation = phase.rOffset;
}
warpPhases[i] = phase;
}
Play_Internal(motionWarpingAsset);
}
public void Stop()
{
_bUpdateWarping = false;
_warpPlayback = 0f;
onWarpEnded.Invoke();
}
public bool IsActive()
{
return _bUpdateWarping;
}
#if UNITY_EDITOR
private List<WarpDebugData> _warpDebugData = new List<WarpDebugData>();
public static void AddWarpDebugData(MotionWarping target, WarpDebugData warpDebugData)
{
if (target == null) return;
target._warpDebugData.Add(warpDebugData);
}
private void OnDrawGizmos()
{
for (int i = 0; i < _warpDebugData.Count; i++)
{
var debugData = _warpDebugData[i];
debugData.onDrawGizmos?.Invoke();
if(debugData.duration < 0f) continue;
// Progress the timer.
debugData.timer = Mathf.Clamp(debugData.timer + Time.deltaTime, 0f, debugData.duration);
_warpDebugData[i] = debugData;
if (Mathf.Approximately(debugData.timer, debugData.duration))
{
_warpDebugData.RemoveAt(i);
i--;
}
}
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3164a26014538e45b7dbfa103dabef0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 100
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
// Designed by KINEMATION, 2024.
using Kinemation.MotionWarping.Runtime.Utility;
using System.Collections.Generic;
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Core
{
[CreateAssetMenu(menuName = "KINEMATION/MotionWarping/WarpingAsset", fileName = "NewWarpingAsset", order = 0)]
public class MotionWarpingAsset : ScriptableObject
{
[Header("Animation")]
public AnimationClip animation;
public AnimationCurve rootX;
public AnimationCurve rootY;
public AnimationCurve rootZ;
public VectorBool useLinear = VectorBool.Enabled;
public VectorBool useAnimation = VectorBool.Enabled;
public VectorBool useWarping = VectorBool.Enabled;
[Tooltip("If true, CharacterController or a RigidBody will be moved with physics enabled.")]
public bool useCollision = false;
[Range(0f, 2f)] public float playRateBasis = 1f;
[Range(1, 10)] public int phasesAmount = 1;
public List<WarpPhase> warpPhases = new List<WarpPhase>();
public Vector3 GetVectorValue(float time)
{
if (rootX == null || rootY == null || rootZ == null)
{
Debug.LogError(name + ": null reference curve!");
return Vector3.zero;
}
return new Vector3(rootX.Evaluate(time), rootY.Evaluate(time), rootZ.Evaluate(time));
}
public float GetLength()
{
if (animation == null)
{
return 0f;
}
return animation.length;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 928e2ecfbd1ec0f469e1838b49a4dda9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,96 @@
// Designed by KINEMATION, 2024.
using System;
using UnityEngine;
namespace KINEMATION.MotionWarping.Runtime.Core
{
[Serializable]
public struct LockState
{
public string controlCurveName;
public Transform boneReference;
[Min(0f)] public float interpSpeed;
[NonSerialized] public float Weight;
[NonSerialized] public Quaternion LockRotation;
[NonSerialized] public Vector3 LockPosition;
[NonSerialized] public bool IsLocked;
[NonSerialized] public bool Interpolate;
}
public class MotionWarpingIk : MonoBehaviour
{
[SerializeField] [Range(0f, 1f)] private float componentWeight = 1f;
[SerializeField] private LockState rightHandLockState;
[SerializeField] private LockState leftHandLockState;
[SerializeField] private LockState rightFootLockState;
[SerializeField] private LockState leftFootLockState;
private Animator _animator;
private void Start()
{
_animator = GetComponent<Animator>();
}
// Updated the current bone lock state.
private void UpdateBoneLock(ref LockState lockState)
{
Transform boneReference = lockState.boneReference;
if (boneReference == null)
{
return;
}
float weight = _animator.GetFloat(lockState.controlCurveName);
if (!lockState.IsLocked && Mathf.Approximately(weight, 1f))
{
lockState.LockPosition = boneReference.position;
lockState.LockRotation = boneReference.rotation;
lockState.IsLocked = true;
lockState.Interpolate = false;
lockState.Weight = 1f;
return;
}
if (lockState.IsLocked && Mathf.Approximately(weight, 0f))
{
lockState.IsLocked = false;
lockState.Interpolate = true;
}
if (!lockState.Interpolate)
{
return;
}
float alpha = 1f - Mathf.Exp(-lockState.interpSpeed * Time.deltaTime);
lockState.Weight = Mathf.Lerp(lockState.Weight, 0f, alpha);
}
private void ApplyLock(ref LockState lockState)
{
Transform tip = lockState.boneReference;
if (tip == null)
{
return;
}
Transform mid = tip.parent;
TwoBoneIk.SolveTwoBoneIK(mid.parent, mid, tip, (lockState.LockPosition, lockState.LockRotation),
mid, lockState.Weight * componentWeight, 1f);
UpdateBoneLock(ref lockState);
}
public void ApplyIK()
{
ApplyLock(ref rightHandLockState);
ApplyLock(ref leftHandLockState);
ApplyLock(ref rightFootLockState);
ApplyLock(ref leftFootLockState);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f2c56b0df3354ea4a3ab76ef0923d868
timeCreated: 1708838078

View File

@@ -0,0 +1,142 @@
// Designed by KINEMATION, 2024.
using UnityEngine;
namespace KINEMATION.MotionWarping.Runtime.Core
{
public class TwoBoneIk
{
private const float FloatMin = 1e-10f;
private const float SqrEpsilon = 1e-8f;
public static float TriangleAngle(float aLen, float aLen1, float aLen2)
{
float c = Mathf.Clamp((aLen1 * aLen1 + aLen2 * aLen2 - aLen * aLen) / (aLen1 * aLen2) / 2.0f, -1.0f, 1.0f);
return Mathf.Acos(c);
}
public static Quaternion FromToRotation(Vector3 from, Vector3 to)
{
float theta = Vector3.Dot(from.normalized, to.normalized);
if (theta >= 1f)
return Quaternion.identity;
if (theta <= -1f)
{
Vector3 axis = Vector3.Cross(from, Vector3.right);
if (axis.sqrMagnitude == 0f)
axis = Vector3.Cross(from, Vector3.up);
return Quaternion.AngleAxis(180f, axis);
}
return Quaternion.AngleAxis(Mathf.Acos(theta) * Mathf.Rad2Deg, Vector3.Cross(from, to).normalized);
}
public static Quaternion NormalizeSafe(Quaternion q)
{
float dot = Quaternion.Dot(q, q);
if (dot > FloatMin)
{
float rsqrt = 1.0f / Mathf.Sqrt(dot);
return new Quaternion(q.x * rsqrt, q.y * rsqrt, q.z * rsqrt, q.w * rsqrt);
}
return Quaternion.identity;
}
public static void SolveTwoBoneIK(
Transform root,
Transform mid,
Transform tip,
(Vector3, Quaternion) target,
Transform hint,
float targetWeight,
float hintWeight
)
{
if (Mathf.Approximately(targetWeight, 0f))
{
return;
}
Vector3 aPosition = root.position;
Vector3 bPosition = mid.position;
Vector3 cPosition = tip.position;
Vector3 tPosition = Vector3.Lerp(cPosition, target.Item1, targetWeight);
Quaternion tRotation = Quaternion.Lerp(tip.rotation, target.Item2, targetWeight);
bool hasHint = hint != null && hintWeight > 0f;
Vector3 ab = bPosition - aPosition;
Vector3 bc = cPosition - bPosition;
Vector3 ac = cPosition - aPosition;
Vector3 at = tPosition - aPosition;
float abLen = ab.magnitude;
float bcLen = bc.magnitude;
float acLen = ac.magnitude;
float atLen = at.magnitude;
float oldAbcAngle = TriangleAngle(acLen, abLen, bcLen);
float newAbcAngle = TriangleAngle(atLen, abLen, bcLen);
// Bend normal strategy is to take whatever has been provided in the animation
// stream to minimize configuration changes, however if this is collinear
// try computing a bend normal given the desired target position.
// If this also fails, try resolving axis using hint if provided.
Vector3 axis = Vector3.Cross(ab, bc);
if (axis.sqrMagnitude < SqrEpsilon)
{
axis = hasHint ? Vector3.Cross(hint.position - aPosition, bc) : Vector3.zero;
if (axis.sqrMagnitude < SqrEpsilon)
axis = Vector3.Cross(at, bc);
if (axis.sqrMagnitude < SqrEpsilon)
axis = Vector3.up;
}
axis = Vector3.Normalize(axis);
float a = 0.5f * (oldAbcAngle - newAbcAngle);
float sin = Mathf.Sin(a);
float cos = Mathf.Cos(a);
Quaternion deltaR = new Quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos);
mid.rotation = deltaR * mid.rotation;
cPosition = tip.position;
ac = cPosition - aPosition;
root.rotation = FromToRotation(ac, at) * root.rotation;
if (hasHint)
{
float acSqrMag = ac.sqrMagnitude;
if (acSqrMag > 0f)
{
bPosition = mid.position;
cPosition = tip.position;
ab = bPosition - aPosition;
ac = cPosition - aPosition;
Vector3 acNorm = ac / Mathf.Sqrt(acSqrMag);
Vector3 ah = hint.position - aPosition;
Vector3 abProj = ab - acNorm * Vector3.Dot(ab, acNorm);
Vector3 ahProj = ah - acNorm * Vector3.Dot(ah, acNorm);
float maxReach = abLen + bcLen;
if (abProj.sqrMagnitude > (maxReach * maxReach * 0.001f) && ahProj.sqrMagnitude > 0f)
{
Quaternion hintR = FromToRotation(abProj, ahProj);
hintR.x *= hintWeight;
hintR.y *= hintWeight;
hintR.z *= hintWeight;
hintR = NormalizeSafe(hintR);
root.rotation = hintR * root.rotation;
}
}
}
tip.rotation = tRotation;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9637a7f6138e4e12a4db8b42a5c0738a
timeCreated: 1708838183

View File

@@ -0,0 +1,62 @@
// Designed by KINEMATION, 2024.
using System.Collections.Generic;
using Kinemation.MotionWarping.Runtime.Utility;
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Core
{
// Obstacle with pre-defined Warp Points
public class WarpObstacle : MonoBehaviour, IWarpPointProvider
{
[SerializeField] private MotionWarpingAsset motionWarpingAsset;
[SerializeField] private List<Transform> points = new List<Transform>();
[SerializeField] private bool useTransforms = false;
[SerializeField] private bool drawDebugPoints = false;
private void OnDrawGizmos()
{
if (!drawDebugPoints) return;
var color = Gizmos.color;
Gizmos.color = Color.green;
foreach (var point in points)
{
if(point == null) continue;
Gizmos.DrawWireSphere(point.position, 0.07f);
}
Gizmos.color = color;
}
public WarpInteractionResult Interact(GameObject instigator)
{
WarpInteractionResult result;
result.points = new WarpPoint[points.Count];
result.asset = motionWarpingAsset;
result.success = points.Count > 0;
for (int i = 0; i < result.points.Length; i++)
{
if (useTransforms)
{
result.points[i] = new WarpPoint()
{
transform = points[i]
};
}
else
{
result.points[i] = new WarpPoint()
{
position = points[i].position,
rotation = points[i].rotation
};
}
}
return result;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ceff3fd151a844759bf3cc1490295ef7
timeCreated: 1694707273

View File

@@ -0,0 +1,24 @@
// Designed by KINEMATION, 2024.
using Kinemation.MotionWarping.Runtime.Utility;
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Core
{
public struct WarpInteractionResult
{
public WarpPoint[] points;
public MotionWarpingAsset asset;
public bool success;
public bool IsValid()
{
return success && points != null && asset != null;
}
}
public interface IWarpPointProvider
{
public WarpInteractionResult Interact(GameObject instigator);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: af4b4f93ff874248959946ba0a83a8ea
timeCreated: 1699699303