Files
Fishing2/Assets/ECM2/Examples/Orient to Ground/Scripts/CharacterOrientToGround.cs
2025-05-11 00:46:26 +08:00

127 lines
4.0 KiB
C#

using UnityEngine;
namespace ECM2.Examples.OrientToGround
{
/// <summary>
/// This example extends a Character (through composition) to adjust a Character's rotation
/// to follow a 'terrain' contour.
/// </summary>
public class CharacterOrientToGround : MonoBehaviour, IColliderFilter
{
public float maxSlopeAngle = 30.0f;
public float alignRate = 10.0f;
public float rayOffset = 0.1f;
public LayerMask groundMask = 1;
[Space(15f)]
public bool drawRays = true;
private readonly RaycastHit[] _hits = new RaycastHit[8];
private Character _character;
// Implement IColliderFilter.
// Ignore character's capsule collider.
public bool Filter(Collider otherCollider)
{
CharacterMovement characterMovement = _character.GetCharacterMovement();
if (otherCollider == characterMovement.collider)
return true;
return false;
}
/// <summary>
/// Computes the average normal sampling a 3x3 area, each ray is a rayOffset distance of other.
/// </summary>
private Vector3 ComputeAverageNormal()
{
CharacterMovement characterMovement = _character.GetCharacterMovement();
Vector3 worldUp = Vector3.up;
Vector3 castOrigin = _character.GetPosition() + worldUp * (characterMovement.height * 0.5f);
Vector3 castDirection = -worldUp;
float castDistance = characterMovement.height;
LayerMask castLayerMask = groundMask;
Vector3 avgNormal = Vector3.zero;
float x = -rayOffset;
float z = -rayOffset;
int hitCount = 0;
for (int i = 0; i < 3; i++)
{
z = -rayOffset;
for (int j = 0; j < 3; j++)
{
bool hit = CollisionDetection.Raycast(castOrigin + new Vector3(x, 0.0f, z), castDirection,
castDistance, castLayerMask, QueryTriggerInteraction.Ignore, out RaycastHit hitResult, _hits, this) > 0;
if (hit)
{
float angle = Vector3.Angle(hitResult.normal, worldUp);
if (angle < maxSlopeAngle)
{
avgNormal += hitResult.normal;
if (drawRays)
Debug.DrawRay(hitResult.point, hitResult.normal, Color.yellow);
hitCount ++;
}
}
z += rayOffset;
}
x += rayOffset;
}
if (hitCount > 0)
avgNormal /= hitCount;
else
avgNormal = worldUp;
if (drawRays)
Debug.DrawRay(_character.GetPosition(), avgNormal * 2f, Color.green);
return avgNormal;
}
private void OnAfterSimulationUpdated(float deltaTime)
{
Vector3 avgNormal = _character.IsWalking() ? ComputeAverageNormal() : Vector3.up;
Quaternion characterRotation = _character.GetRotation();
Vector3 characterUp = characterRotation * Vector3.up;
Quaternion slopeRotation = Quaternion.FromToRotation(characterUp, avgNormal);
characterRotation = Quaternion.Slerp(characterRotation, slopeRotation * characterRotation, alignRate * deltaTime);
_character.SetRotation(characterRotation);
}
private void Awake()
{
_character = GetComponent<Character>();
}
private void OnEnable()
{
_character.AfterSimulationUpdated += OnAfterSimulationUpdated;
}
private void OnDisable()
{
_character.AfterSimulationUpdated -= OnAfterSimulationUpdated;
}
}
}