Improved animations
This commit is contained in:
parent
6fab0a35cf
commit
dce7835dfc
@ -23,8 +23,33 @@ namespace Passer.CreatureControl {
|
||||
/// </summary>
|
||||
public Quaternion targetToModelRotation;
|
||||
|
||||
public Animator animator;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum height of objects from the ground which do not stop the creature
|
||||
/// </summary>
|
||||
public float stepOffset = 0.3F;
|
||||
|
||||
/// <summary>
|
||||
/// If there is not static object below the feet of the avatar the avatar will fall down until it reaches solid ground
|
||||
/// </summary>
|
||||
public bool useGravity = true;
|
||||
[Range(0f, 1f)]
|
||||
public float slopeAlignment = 0.3f;
|
||||
|
||||
/// <summary>
|
||||
/// The velocity caused by gravity
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
protected Vector3 fallSpeed;
|
||||
|
||||
#region Init
|
||||
|
||||
protected virtual void Awake() {
|
||||
CheckColliders();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ensure a target rig is available
|
||||
/// </summary>
|
||||
@ -76,7 +101,7 @@ namespace Passer.CreatureControl {
|
||||
else
|
||||
// Oops! There are multiple renders with different parents....
|
||||
Debug.LogWarning("Unclear model root");
|
||||
// We still return a model root, but this may not be the correct one...
|
||||
// We still return a model root, but this may not be the correct one...
|
||||
}
|
||||
return this.model != null;
|
||||
}
|
||||
@ -115,6 +140,16 @@ namespace Passer.CreatureControl {
|
||||
targetRig.transform.SetPositionAndRotation(this.transform.position, this.transform.rotation);
|
||||
}
|
||||
|
||||
// void OnAnimatorMove() {
|
||||
// if (animator == null || creatureRigidbody == null || creatureRigidbody.isKinematic )
|
||||
// return;
|
||||
|
||||
// Vector3 delta = animator.deltaPosition;
|
||||
// Quaternion rot = animator.deltaRotation;
|
||||
// creatureRigidbody.MovePosition(creatureRigidbody.position + delta);
|
||||
// creatureRigidbody.MoveRotation(creatureRigidbody.rotation * rot);
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Update the pose of the creature using the target rig
|
||||
/// </summary>
|
||||
@ -135,6 +170,158 @@ namespace Passer.CreatureControl {
|
||||
this.model.SetPositionAndRotation(newPosition, newOrientation);
|
||||
}
|
||||
|
||||
#region Collisions
|
||||
|
||||
[HideInInspector]
|
||||
public Rigidbody creatureRigidbody;
|
||||
|
||||
[HideInInspector]
|
||||
public CapsuleCollider bodyCollider;
|
||||
[HideInInspector]
|
||||
public float colliderToGround;
|
||||
|
||||
private void CheckColliders() {
|
||||
creatureRigidbody = this.GetComponent<Rigidbody>();
|
||||
|
||||
if (bodyCollider != null)
|
||||
return;
|
||||
|
||||
bodyCollider = this.GetComponent<CapsuleCollider>();
|
||||
|
||||
// Assuming transform.position.y is on the ground
|
||||
if (bodyCollider != null) {
|
||||
Vector3 centerWorld = transform.TransformPoint(bodyCollider.center);
|
||||
colliderToGround = centerWorld.y - transform.position.y;
|
||||
colliderToGround -= bodyCollider.direction switch {
|
||||
0 => bodyCollider.radius,
|
||||
1 => bodyCollider.height / 2,
|
||||
_ => bodyCollider.radius
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Collisions
|
||||
|
||||
#region Ground
|
||||
|
||||
/// <summary>
|
||||
/// The ground Transform on which the pawn is standing
|
||||
/// </summary>
|
||||
/// When the pawn is not standing on the ground, the value is null
|
||||
public Transform ground;
|
||||
|
||||
protected readonly float rotationSmoothTime = 0.06f;
|
||||
protected void CheckGrounded() {
|
||||
Vector3 footBase = this.transform.position;
|
||||
|
||||
float distance = GetDistanceToGround(stepOffset, out ground, out Vector3 groundNormal);
|
||||
if (distance <= 0) {
|
||||
// We are on or under the ground
|
||||
this.fallSpeed = Vector3.zero;
|
||||
if (distance <= -stepOffset)
|
||||
Debug.Log($"d: {-distance}");
|
||||
// Ensure that we are on the ground again
|
||||
if (creatureRigidbody != null && creatureRigidbody.isKinematic == false) {
|
||||
float requiredV0 = (-distance - 0.5f * Physics.gravity.y * Time.fixedDeltaTime * Time.fixedDeltaTime) / Time.fixedDeltaTime;
|
||||
requiredV0 = Mathf.Clamp(requiredV0, -20, 20);
|
||||
Vector3 v = creatureRigidbody.velocity;
|
||||
v.y = requiredV0;
|
||||
creatureRigidbody.velocity = v;
|
||||
}
|
||||
else
|
||||
transform.Translate(0, -distance, 0, Space.World);
|
||||
}
|
||||
else {
|
||||
// We are above the ground
|
||||
ground = null;
|
||||
if (useGravity)
|
||||
Fall(distance);
|
||||
}
|
||||
if (slopeAlignment > 0f) {
|
||||
Vector3 forward = Vector3.ProjectOnPlane(this.transform.forward, groundNormal).normalized;
|
||||
if (forward.sqrMagnitude < 0.001f)
|
||||
forward = this.transform.forward;
|
||||
Quaternion targetRot = Quaternion.LookRotation(forward, groundNormal);
|
||||
// blend between no-tilt and full alignment
|
||||
targetRot = Quaternion.Slerp(this.transform.rotation, targetRot, slopeAlignment);
|
||||
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, targetRot, 1f - Mathf.Exp(-Time.deltaTime / rotationSmoothTime));
|
||||
}
|
||||
}
|
||||
|
||||
protected void Fall(float distanceToGround) {
|
||||
Vector3 translation = fallSpeed * Time.deltaTime;
|
||||
if (translation.magnitude > distanceToGround)
|
||||
translation = Physics.gravity.normalized * distanceToGround;
|
||||
|
||||
transform.Translate(translation);
|
||||
|
||||
fallSpeed += Physics.gravity * Time.deltaTime;
|
||||
}
|
||||
|
||||
public float GetDistanceToGround(float maxDistance, out Transform ground, out Vector3 normal) {
|
||||
normal = Physics.gravity.normalized;
|
||||
|
||||
Vector3 rayDirection = -normal;
|
||||
|
||||
int layerMask = Physics.DefaultRaycastLayers;
|
||||
float distance = maxDistance;
|
||||
RaycastHit[] hits;
|
||||
|
||||
// - Why use Ray/CapsuleCast All?
|
||||
// Because I need to ignore my own colliders and
|
||||
// I don't want to force users to add some layer before this can be used
|
||||
if (bodyCollider != null) {
|
||||
Vector3 centerWorld = transform.TransformPoint(bodyCollider.center);
|
||||
float half = (bodyCollider.height * 0.5f) - bodyCollider.radius;
|
||||
Vector3 up = bodyCollider.direction switch {
|
||||
0 => transform.TransformDirection(Vector3.right),
|
||||
1 => transform.TransformDirection(Vector3.up),
|
||||
_ => transform.TransformDirection(Vector3.forward),
|
||||
};
|
||||
Vector3 p1 = centerWorld + up * half - normal * maxDistance; // top point
|
||||
Vector3 p2 = centerWorld - up * half - normal * maxDistance; // bottom point
|
||||
|
||||
// Debug.DrawRay(p1, 2 * maxDistance * -rayDirection, Color.magenta);
|
||||
// Debug.DrawRay(p2, 2 * maxDistance * -rayDirection, Color.magenta);
|
||||
hits = Physics.CapsuleCastAll(p1, p2, bodyCollider.radius, -rayDirection, maxDistance * 2, layerMask, QueryTriggerInteraction.Ignore);
|
||||
maxDistance += colliderToGround;
|
||||
}
|
||||
else {
|
||||
Vector3 rayStart = this.transform.position + normal * maxDistance;
|
||||
// Debug.DrawRay(rayStart, 2 * maxDistance * rayDirection, Color.magenta);
|
||||
hits = Physics.RaycastAll(rayStart, rayDirection, maxDistance * 2, layerMask, QueryTriggerInteraction.Ignore);
|
||||
}
|
||||
|
||||
if (hits.Length == 0) {
|
||||
ground = null;
|
||||
return maxDistance;
|
||||
}
|
||||
|
||||
int closestHitIx = 0;
|
||||
bool foundClosest = false;
|
||||
for (int i = 0; i < hits.Length; i++) {
|
||||
if (hits[i].rigidbody == null &&
|
||||
hits[i].distance > 0 &&
|
||||
hits[i].distance <= hits[closestHitIx].distance) {
|
||||
closestHitIx = i;
|
||||
foundClosest = true;
|
||||
}
|
||||
}
|
||||
if (!foundClosest) {
|
||||
ground = null;
|
||||
return maxDistance;
|
||||
}
|
||||
|
||||
RaycastHit closestHit = hits[closestHitIx];
|
||||
Debug.Log($"hit.distance {closestHit.distance} - {maxDistance}");
|
||||
ground = closestHit.transform;
|
||||
normal = closestHit.normal;
|
||||
distance = closestHit.distance - maxDistance;
|
||||
return distance;
|
||||
}
|
||||
|
||||
#endregion Ground
|
||||
|
||||
#endregion Update
|
||||
|
||||
#region Scene view
|
||||
|
||||
@ -27,13 +27,13 @@ namespace Passer.CreatureControl {
|
||||
public Receptor foodReceptor;
|
||||
public Receptor homeReceptor;
|
||||
|
||||
public Animator animator;
|
||||
public Vector3 linearVelocity;
|
||||
public Vector3 angularVelocity;
|
||||
|
||||
#region Init
|
||||
|
||||
protected virtual void Awake() {
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
if (this.targetRig != null)
|
||||
this.animator = this.targetRig.animator;
|
||||
|
||||
@ -100,26 +100,50 @@ namespace Passer.CreatureControl {
|
||||
|
||||
Vector3 localForce = nanoBrain.brain.defaultOutput.outputValue;
|
||||
this.linearVelocity = (1 - inertia) * (Time.deltaTime * localForce.normalized) + inertia * this.linearVelocity;
|
||||
this.linearVelocity = this.linearVelocity.normalized * 0.2f;
|
||||
this.linearVelocity = this.linearVelocity.normalized; // * this.forwardSpeed;
|
||||
|
||||
this.animator.SetFloat("Forward", this.forwardSpeed); //this.linearVelocity.z * this.forwardSpeed);
|
||||
//this.animator.SetFloat("Forward", this.forwardSpeed); //this.linearVelocity.z * this.forwardSpeed);
|
||||
float forwardParam = Mathf.Clamp01(this.linearVelocity.z); // / this.forwardSpeed);
|
||||
float angleDeg = 0;
|
||||
if (this.linearVelocity.magnitude > 1e-5f)
|
||||
angleDeg = Mathf.Atan2(this.linearVelocity.x, this.linearVelocity.z) * Mathf.Rad2Deg;
|
||||
|
||||
// base turn in -1..1
|
||||
float baseTurn = Mathf.Clamp(angleDeg / 45, -1f, 1f);
|
||||
float turnParam = baseTurn; // * Mathf.Max(0.6f, 1 - forwardParam);
|
||||
|
||||
// Rotate towards the movement direction
|
||||
if (this.linearVelocity != Vector3.zero) {
|
||||
Quaternion targetRotation = Quaternion.LookRotation(this.linearVelocity);
|
||||
Quaternion worldRotation = transform.rotation * targetRotation;
|
||||
Quaternion deltaRotation = worldRotation * Quaternion.Inverse(transform.rotation);
|
||||
// if (this.linearVelocity != Vector3.zero) {
|
||||
// Quaternion targetRotation = Quaternion.LookRotation(this.linearVelocity);
|
||||
// Quaternion worldRotation = transform.rotation * targetRotation;
|
||||
// Quaternion deltaRotation = worldRotation * Quaternion.Inverse(transform.rotation);
|
||||
|
||||
Vector3 eulerAngleChange = deltaRotation.eulerAngles;
|
||||
// Normalize the Euler angles to avoid unexpected jumps due to 360-degree rotations
|
||||
eulerAngleChange = new Vector3(
|
||||
LinearAlgebra.Angles.Normalize(eulerAngleChange.x),
|
||||
LinearAlgebra.Angles.Normalize(eulerAngleChange.y),
|
||||
LinearAlgebra.Angles.Normalize(eulerAngleChange.z)
|
||||
);
|
||||
// Vector3 eulerAngleChange = deltaRotation.eulerAngles;
|
||||
// // Normalize the Euler angles to avoid unexpected jumps due to 360-degree rotations
|
||||
// eulerAngleChange = new Vector3(
|
||||
// LinearAlgebra.Angles.Normalize(eulerAngleChange.x),
|
||||
// LinearAlgebra.Angles.Normalize(eulerAngleChange.y),
|
||||
// LinearAlgebra.Angles.Normalize(eulerAngleChange.z)
|
||||
// );
|
||||
|
||||
this.animator.SetFloat("Rotate", eulerAngleChange.y / 45 * this.rotationSpeed);
|
||||
}
|
||||
// float rotSpeed = (eulerAngleChange.y / 45) * this.rotationSpeed;
|
||||
// this.animator.SetFloat("Rotate", rotSpeed);
|
||||
// Debug.Log($"fw {this.forwardSpeed} ang {rotSpeed}");
|
||||
// }
|
||||
|
||||
// Smooth against current animator values
|
||||
// float curF = animator.GetFloat("Forward");
|
||||
// float curT = animator.GetFloat("Turn");
|
||||
// forwardParam = Mathf.Lerp(curF, forwardParam, 1f - Mathf.Exp(-10 * Time.deltaTime));
|
||||
// turnParam = Mathf.Lerp(curT, turnParam, 1f - Mathf.Exp(-10 * Time.deltaTime));
|
||||
|
||||
|
||||
this.animator.SetFloat("Forward", forwardParam);
|
||||
this.animator.SetFloat("Rotate", turnParam);
|
||||
}
|
||||
|
||||
public virtual void FixedUpdate() {
|
||||
CheckGrounded();
|
||||
}
|
||||
|
||||
public float beatInterval = 3;
|
||||
|
||||
@ -25,7 +25,7 @@ BlendTree:
|
||||
m_DirectBlendParameter: Forward
|
||||
m_Mirror: 0
|
||||
- serializedVersion: 2
|
||||
m_Motion: {fileID: 0}
|
||||
m_Motion: {fileID: 7400000, guid: ab82ff68e62ea3b1c8e6523f8d46c142, type: 2}
|
||||
m_Threshold: 0.6666667
|
||||
m_Position: {x: -1, y: 0}
|
||||
m_TimeScale: 1
|
||||
@ -33,7 +33,7 @@ BlendTree:
|
||||
m_DirectBlendParameter: Forward
|
||||
m_Mirror: 0
|
||||
- serializedVersion: 2
|
||||
m_Motion: {fileID: 0}
|
||||
m_Motion: {fileID: 7400000, guid: 91229db5e929c379bbfd5bf417848488, type: 2}
|
||||
m_Threshold: 1
|
||||
m_Position: {x: 1, y: 0}
|
||||
m_TimeScale: 1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user