Improved animations
This commit is contained in:
parent
6fab0a35cf
commit
dce7835dfc
@ -23,8 +23,33 @@ namespace Passer.CreatureControl {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Quaternion targetToModelRotation;
|
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
|
#region Init
|
||||||
|
|
||||||
|
protected virtual void Awake() {
|
||||||
|
CheckColliders();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensure a target rig is available
|
/// Ensure a target rig is available
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -115,6 +140,16 @@ namespace Passer.CreatureControl {
|
|||||||
targetRig.transform.SetPositionAndRotation(this.transform.position, this.transform.rotation);
|
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>
|
/// <summary>
|
||||||
/// Update the pose of the creature using the target rig
|
/// Update the pose of the creature using the target rig
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -135,6 +170,158 @@ namespace Passer.CreatureControl {
|
|||||||
this.model.SetPositionAndRotation(newPosition, newOrientation);
|
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
|
#endregion Update
|
||||||
|
|
||||||
#region Scene view
|
#region Scene view
|
||||||
|
|||||||
@ -27,13 +27,13 @@ namespace Passer.CreatureControl {
|
|||||||
public Receptor foodReceptor;
|
public Receptor foodReceptor;
|
||||||
public Receptor homeReceptor;
|
public Receptor homeReceptor;
|
||||||
|
|
||||||
public Animator animator;
|
|
||||||
public Vector3 linearVelocity;
|
public Vector3 linearVelocity;
|
||||||
public Vector3 angularVelocity;
|
public Vector3 angularVelocity;
|
||||||
|
|
||||||
#region Init
|
#region Init
|
||||||
|
|
||||||
protected virtual void Awake() {
|
protected override void Awake() {
|
||||||
|
base.Awake();
|
||||||
if (this.targetRig != null)
|
if (this.targetRig != null)
|
||||||
this.animator = this.targetRig.animator;
|
this.animator = this.targetRig.animator;
|
||||||
|
|
||||||
@ -100,26 +100,50 @@ namespace Passer.CreatureControl {
|
|||||||
|
|
||||||
Vector3 localForce = nanoBrain.brain.defaultOutput.outputValue;
|
Vector3 localForce = nanoBrain.brain.defaultOutput.outputValue;
|
||||||
this.linearVelocity = (1 - inertia) * (Time.deltaTime * localForce.normalized) + inertia * this.linearVelocity;
|
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
|
// Rotate towards the movement direction
|
||||||
if (this.linearVelocity != Vector3.zero) {
|
// if (this.linearVelocity != Vector3.zero) {
|
||||||
Quaternion targetRotation = Quaternion.LookRotation(this.linearVelocity);
|
// Quaternion targetRotation = Quaternion.LookRotation(this.linearVelocity);
|
||||||
Quaternion worldRotation = transform.rotation * targetRotation;
|
// Quaternion worldRotation = transform.rotation * targetRotation;
|
||||||
Quaternion deltaRotation = worldRotation * Quaternion.Inverse(transform.rotation);
|
// Quaternion deltaRotation = worldRotation * Quaternion.Inverse(transform.rotation);
|
||||||
|
|
||||||
Vector3 eulerAngleChange = deltaRotation.eulerAngles;
|
// Vector3 eulerAngleChange = deltaRotation.eulerAngles;
|
||||||
// Normalize the Euler angles to avoid unexpected jumps due to 360-degree rotations
|
// // Normalize the Euler angles to avoid unexpected jumps due to 360-degree rotations
|
||||||
eulerAngleChange = new Vector3(
|
// eulerAngleChange = new Vector3(
|
||||||
LinearAlgebra.Angles.Normalize(eulerAngleChange.x),
|
// LinearAlgebra.Angles.Normalize(eulerAngleChange.x),
|
||||||
LinearAlgebra.Angles.Normalize(eulerAngleChange.y),
|
// LinearAlgebra.Angles.Normalize(eulerAngleChange.y),
|
||||||
LinearAlgebra.Angles.Normalize(eulerAngleChange.z)
|
// 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;
|
public float beatInterval = 3;
|
||||||
|
|||||||
@ -25,7 +25,7 @@ BlendTree:
|
|||||||
m_DirectBlendParameter: Forward
|
m_DirectBlendParameter: Forward
|
||||||
m_Mirror: 0
|
m_Mirror: 0
|
||||||
- serializedVersion: 2
|
- serializedVersion: 2
|
||||||
m_Motion: {fileID: 0}
|
m_Motion: {fileID: 7400000, guid: ab82ff68e62ea3b1c8e6523f8d46c142, type: 2}
|
||||||
m_Threshold: 0.6666667
|
m_Threshold: 0.6666667
|
||||||
m_Position: {x: -1, y: 0}
|
m_Position: {x: -1, y: 0}
|
||||||
m_TimeScale: 1
|
m_TimeScale: 1
|
||||||
@ -33,7 +33,7 @@ BlendTree:
|
|||||||
m_DirectBlendParameter: Forward
|
m_DirectBlendParameter: Forward
|
||||||
m_Mirror: 0
|
m_Mirror: 0
|
||||||
- serializedVersion: 2
|
- serializedVersion: 2
|
||||||
m_Motion: {fileID: 0}
|
m_Motion: {fileID: 7400000, guid: 91229db5e929c379bbfd5bf417848488, type: 2}
|
||||||
m_Threshold: 1
|
m_Threshold: 1
|
||||||
m_Position: {x: 1, y: 0}
|
m_Position: {x: 1, y: 0}
|
||||||
m_TimeScale: 1
|
m_TimeScale: 1
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user