mirror of
https://github.com/appen-isen/jeu-sans-image.git
synced 2026-03-18 21:50:42 +01:00
Add: Feet controller
Added feet controller. Handles feet movement in accordance with player movement.
This commit is contained in:
227
Assets/Scripts/Control/FeetController.cs
Normal file
227
Assets/Scripts/Control/FeetController.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
public sealed class FeetController : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Rigidbody playerRb;
|
||||
[SerializeField] private float acceptableRestingDistance = 0.3f;
|
||||
[SerializeField] private float footLiftDelay = 0.1f;
|
||||
[SerializeField] private float footBackToGroundDelay = 0.5f;
|
||||
[SerializeField] private float feetDistanceToCenter = 0.2f;
|
||||
[SerializeField] private float stepDistanceMultiplier = 1f;
|
||||
[SerializeField] private float maxStepHeight = 1f;
|
||||
[SerializeField] private float stepMinDistanceToWalls = 0.1f;
|
||||
[SerializeField] private float distanceUnderFootFloorDetection = 0.1f;
|
||||
|
||||
private Vector3 leftFootPosition = Vector3.zero;
|
||||
private Vector3 rightFootPosition = Vector3.zero;
|
||||
|
||||
private bool isRightFootNextToLift = true; // Updated when lifting a foot
|
||||
private bool isAtRest = true;
|
||||
private bool isLiftingFoot = false;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public const float feetRadius = 0.15f;
|
||||
[SerializeField] private GameObject leftfoot_debug;
|
||||
[SerializeField] private GameObject rightfoot_debug;
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Handles.color = Color.magenta;
|
||||
Handles.DrawSolidDisc(leftFootPosition, Vector3.up, feetRadius);
|
||||
Handles.DrawSolidDisc(rightFootPosition, Vector3.up, feetRadius);
|
||||
}
|
||||
#endif
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ResetFeetPositions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the feet position without playing sounds.
|
||||
/// Should be used for init or reset purposes only, as otherwise footsteps might sound odd.
|
||||
/// </summary>
|
||||
private void ResetFeetPositions()
|
||||
{
|
||||
(leftFootPosition, rightFootPosition) = GetFeetRestPosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the expected feet location if we were at rest
|
||||
/// </summary>
|
||||
/// <returns>Tuple or expected feet location: (expectedLeft, expectedRight)</returns>
|
||||
private (Vector3, Vector3) GetFeetRestPosition()
|
||||
{
|
||||
Vector3 feetCenter = transform.position;
|
||||
Vector3 towardsRight = transform.right;
|
||||
Vector3 expectedLeft = feetCenter - feetDistanceToCenter*towardsRight;
|
||||
Vector3 expectedRight = feetCenter + feetDistanceToCenter*towardsRight;
|
||||
return (expectedLeft, expectedRight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Know whether feet are in rest position or not
|
||||
/// </summary>
|
||||
/// <returns>Whether feet are in rest position or not</returns>
|
||||
private bool AreFeetInRestPosition()
|
||||
{
|
||||
var (expectedLeft, expectedRight) = GetFeetRestPosition();
|
||||
return (
|
||||
Vector3.Distance(expectedLeft, leftFootPosition) < acceptableRestingDistance
|
||||
|| Vector3.Distance(expectedRight, rightFootPosition) < acceptableRestingDistance
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lift the foot. Says it all.
|
||||
/// </summary>
|
||||
private void LiftFoot()
|
||||
{
|
||||
if (isLiftingFoot) return; // Can't lift two feet at the same time
|
||||
isLiftingFoot = true;
|
||||
|
||||
Vector3 liftSoundPosition = isRightFootNextToLift ? rightFootPosition : leftFootPosition;
|
||||
isRightFootNextToLift = ! isRightFootNextToLift;
|
||||
|
||||
// Trigger sound at position
|
||||
GameObject floor = getFloorUnderPosition(liftSoundPosition);
|
||||
if (floor == null) return; // No floor under foot
|
||||
|
||||
TriggerFootLift footLift = floor.GetComponent<TriggerFootLift>();
|
||||
if (footLift == null) return; // No foot lifting behavior
|
||||
|
||||
footLift.OnFootLift(liftSoundPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Predict where the next foot to place should be placed
|
||||
/// Returns a Vector3: predicted position, or rest position when no suitable position found
|
||||
/// Returns a Collider: collider the foot is placed on, or null if no suitable position found
|
||||
/// </summary>
|
||||
/// <returns>Whether the foot can be placed forward ; where it should be placed ; the collider under the foot</returns>
|
||||
private (Vector3, Collider) PredictFootPosition()
|
||||
{
|
||||
// Get position at rest
|
||||
var (restLeft, restRight) = GetFeetRestPosition();
|
||||
// We want the foot opposite to the next to lift (we want the next to place on ground)
|
||||
Vector3 restFoot = isRightFootNextToLift ? restLeft : restRight;
|
||||
|
||||
// Project position forward, in moving direction
|
||||
Vector3 stepMovement = playerRb.linearVelocity * stepDistanceMultiplier;
|
||||
|
||||
Vector3 projectedPosition;
|
||||
// Test for walls: feet can't go through walls
|
||||
if (Physics.Raycast(restFoot, stepMovement, out RaycastHit hit, stepMovement.magnitude)) // TODO wall mask
|
||||
{
|
||||
// There is a wall => foot cannot go through. Must place it in front of the wall.
|
||||
projectedPosition = restFoot + stepMovement.normalized * (hit.distance - stepMinDistanceToWalls);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No wall => can take a step forward
|
||||
projectedPosition = restFoot + stepMovement;
|
||||
}
|
||||
|
||||
// Raycast downwards to take slopes into account
|
||||
Vector3 rayStart = projectedPosition + Vector3.up * maxStepHeight;
|
||||
if (Physics.Raycast(rayStart, Vector3.down, out hit, maxStepHeight*2)) // TODO floor mask
|
||||
{
|
||||
// Found ground to place foot on at raycast hit point
|
||||
return (hit.point, hit.collider);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No ground to place foot on => use rest position as a backup
|
||||
return (restFoot, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Place a foot on ground. Says it all.
|
||||
/// </summary>
|
||||
private void PlaceFootOnGround()
|
||||
{
|
||||
if (!isLiftingFoot) return; // Can't place foot on ground if none are lifted
|
||||
isLiftingFoot = false;
|
||||
|
||||
var (predictedFootPosition, underFootCollider) = PredictFootPosition();
|
||||
if (isRightFootNextToLift)
|
||||
{
|
||||
leftFootPosition = predictedFootPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
rightFootPosition = predictedFootPosition;
|
||||
}
|
||||
|
||||
// Activate colliders at new foot location
|
||||
if (underFootCollider != null)
|
||||
{
|
||||
// Walking on a collider
|
||||
TriggerFootstep trigger = underFootCollider.GetComponent<TriggerFootstep>();
|
||||
if (trigger != null)
|
||||
{
|
||||
trigger.OnFootstep(predictedFootPosition);
|
||||
}
|
||||
// Otherwise, collider is not supposed to be walked on (edge case)
|
||||
}
|
||||
// Otherwise, no collider found under foot, we should probably do nothing (edge case)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the floor gameobject that is under the given position
|
||||
/// </summary>
|
||||
/// <param name="position">The position to check</param>
|
||||
/// <returns>The floor object that is under the given position, or null if foot is not on a floor</returns>
|
||||
private GameObject getFloorUnderPosition(Vector3 position) {
|
||||
Vector3 rayStart = position + distanceUnderFootFloorDetection*Vector3.down;
|
||||
if (Physics.Raycast(rayStart, Vector3.down, out RaycastHit hit, distanceUnderFootFloorDetection*2)) { // TODO floor mask
|
||||
return hit.collider.gameObject;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walk until at rest again
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator WalkCoroutine()
|
||||
{
|
||||
isAtRest = false;
|
||||
bool isWalking = true;
|
||||
while (isWalking)
|
||||
{
|
||||
LiftFoot();
|
||||
yield return new WaitForSeconds(footBackToGroundDelay); // Wait after lifting to foot
|
||||
|
||||
PlaceFootOnGround();
|
||||
yield return new WaitForSeconds(footLiftDelay); // Wait before lifting the foot again
|
||||
if (AreFeetInRestPosition())
|
||||
{
|
||||
isWalking = false;
|
||||
}
|
||||
}
|
||||
isAtRest = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update performed every physics frame
|
||||
/// </summary>
|
||||
private void FixedUpdate()
|
||||
{
|
||||
// When at rest, start moving feet when leaving acceptable resting distance
|
||||
if (isAtRest)
|
||||
{
|
||||
if (! AreFeetInRestPosition())
|
||||
{
|
||||
StartCoroutine(WalkCoroutine());
|
||||
}
|
||||
}
|
||||
leftfoot_debug.transform.position = leftFootPosition;
|
||||
rightfoot_debug.transform.position = rightFootPosition;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Control/FeetController.cs.meta
Normal file
2
Assets/Scripts/Control/FeetController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c1ad1d866e9c78439fa3d7b156ff06a
|
||||
Reference in New Issue
Block a user