359 lines
14 KiB
C#
359 lines
14 KiB
C#
using System.Collections.Generic;
|
|
using Alchemy.Inspector;
|
|
using KinematicCharacterController;
|
|
using LitMotion;
|
|
using LitMotion.Extensions;
|
|
using R3;
|
|
using UnityEngine;
|
|
|
|
namespace Code.Game.Gameplay.Player
|
|
{
|
|
public enum OrientationMethod
|
|
{
|
|
TowardsCamera,
|
|
TowardsMovement,
|
|
}
|
|
|
|
public struct PlayerCharacterInputs
|
|
{
|
|
public float MoveAxisForward;
|
|
public float MoveAxisRight;
|
|
public Quaternion CameraRotation;
|
|
}
|
|
|
|
public struct AICharacterInputs
|
|
{
|
|
public Vector3 MoveVector;
|
|
public Quaternion Look;
|
|
}
|
|
|
|
public enum BonusOrientationMethod
|
|
{
|
|
None,
|
|
TowardsGravity,
|
|
TowardsGroundSlopeAndGravity,
|
|
}
|
|
|
|
public class PlayerMovementController : MonoBehaviour, ICharacterController
|
|
{
|
|
public KinematicCharacterMotor Motor;
|
|
|
|
[TabGroup("Stable Movement")] public float MaxStableMoveSpeed = 3f;
|
|
[TabGroup("Stable Movement")] public float StableMovementSharpness = 15f;
|
|
[TabGroup("Stable Movement")] public float OrientationSharpness = 10f;
|
|
[TabGroup("Stable Movement")] public OrientationMethod OrientationMethod = OrientationMethod.TowardsCamera;
|
|
|
|
[TabGroup("Air Movement")] public float MaxAirMoveSpeed = 15f;
|
|
[TabGroup("Air Movement")] public float AirAccelerationSpeed = 15f;
|
|
[TabGroup("Air Movement")] public float Drag = 0.1f;
|
|
|
|
[TabGroup("Jumping")] public BonusOrientationMethod BonusOrientationMethod = BonusOrientationMethod.None;
|
|
[TabGroup("Jumping")] public float BonusOrientationSharpness = 10f;
|
|
[TabGroup("Jumping")] public float BonusOrientationGravitySharpness;
|
|
[TabGroup("Jumping")] public Vector3 Gravity = new Vector3(0, -30f, 0);
|
|
|
|
[TabGroup("Misc")] public List<Collider> IgnoredColliders = new List<Collider>();
|
|
public Vector3 CurrentVelocity => Motor.Velocity;
|
|
public Quaternion CurrentRotation => Motor.TransientRotation;
|
|
|
|
private Vector3 _moveInputVector;
|
|
private Vector3 _lookInputVector;
|
|
|
|
private Vector3 _internalVelocityAdd = Vector3.zero;
|
|
|
|
|
|
private void Awake()
|
|
{
|
|
// Assign the characterController to the motor
|
|
Motor.CharacterController = this;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// This is called every frame by ExamplePlayer in order to tell the character what its inputs are
|
|
/// </summary>
|
|
public void SetInputs(ref PlayerCharacterInputs inputs)
|
|
{
|
|
// Clamp input
|
|
Vector3 moveInputVector =
|
|
Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0f, inputs.MoveAxisForward), 1f);
|
|
|
|
// Calculate camera direction and rotation on the character plane
|
|
Vector3 cameraPlanarDirection =
|
|
Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.forward, Motor.CharacterUp).normalized;
|
|
if (cameraPlanarDirection.sqrMagnitude == 0f)
|
|
{
|
|
cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, Motor.CharacterUp)
|
|
.normalized;
|
|
}
|
|
|
|
Quaternion cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, Motor.CharacterUp);
|
|
|
|
// Move and look inputs
|
|
_moveInputVector = cameraPlanarRotation * moveInputVector;
|
|
|
|
switch (OrientationMethod)
|
|
{
|
|
case OrientationMethod.TowardsCamera:
|
|
_lookInputVector = cameraPlanarDirection;
|
|
break;
|
|
case OrientationMethod.TowardsMovement:
|
|
_lookInputVector = _moveInputVector.normalized;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is called every frame by the AI script in order to tell the character what its inputs are
|
|
/// </summary>
|
|
public void SetInputs(ref AICharacterInputs inputs)
|
|
{
|
|
var cameraPlanarDirection =
|
|
Vector3.ProjectOnPlane(inputs.Look * Vector3.forward, Motor.CharacterUp).normalized;
|
|
|
|
if (cameraPlanarDirection.sqrMagnitude == 0f)
|
|
{
|
|
cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.Look * Vector3.up, Motor.CharacterUp)
|
|
.normalized;
|
|
}
|
|
|
|
_moveInputVector = inputs.MoveVector;
|
|
_lookInputVector = cameraPlanarDirection;
|
|
}
|
|
|
|
private Quaternion _tmpTransientRot;
|
|
private MotionHandle _crouchTransition;
|
|
|
|
/// <summary>
|
|
/// (Called by KinematicCharacterMotor during its update cycle)
|
|
/// This is called before the character begins its movement update
|
|
/// </summary>
|
|
public void BeforeCharacterUpdate(float deltaTime)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// (Called by KinematicCharacterMotor during its update cycle)
|
|
/// This is where you tell your character what its rotation should be right now.
|
|
/// This is the ONLY place where you should set the character's rotation
|
|
/// </summary>
|
|
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
|
|
{
|
|
if (_lookInputVector.sqrMagnitude > 0f && OrientationSharpness > 0f)
|
|
{
|
|
// Smoothly interpolate from current to target look direction
|
|
|
|
Vector3 smoothedLookInputDirection = Vector3.Slerp(Motor.CharacterForward, _lookInputVector,
|
|
1 - Mathf.Exp(-OrientationSharpness * deltaTime)).normalized;
|
|
|
|
// Set the current rotation (which will be used by the KinematicCharacterMotor)
|
|
currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, Motor.CharacterUp);
|
|
}
|
|
|
|
Vector3 currentUp = (currentRotation * Vector3.up);
|
|
if (BonusOrientationMethod == BonusOrientationMethod.TowardsGravity)
|
|
{
|
|
// Rotate from current up to invert gravity
|
|
Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, -Gravity.normalized,
|
|
1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime));
|
|
currentRotation = Quaternion.FromToRotation(currentUp, smoothedGravityDir) * currentRotation;
|
|
}
|
|
else if (BonusOrientationMethod == BonusOrientationMethod.TowardsGroundSlopeAndGravity)
|
|
{
|
|
if (Motor.GroundingStatus.IsStableOnGround)
|
|
{
|
|
Vector3 initialCharacterBottomHemiCenter =
|
|
Motor.TransientPosition + (currentUp * Motor.Capsule.radius);
|
|
|
|
Vector3 smoothedGroundNormal = Vector3.Slerp(Motor.CharacterUp,
|
|
Motor.GroundingStatus.GroundNormal,
|
|
1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime));
|
|
currentRotation = Quaternion.FromToRotation(currentUp, smoothedGroundNormal) *
|
|
currentRotation;
|
|
|
|
// Move the position to create a rotation around the bottom hemi center instead of around the pivot
|
|
Motor.SetTransientPosition(initialCharacterBottomHemiCenter +
|
|
(currentRotation * Vector3.down * Motor.Capsule.radius));
|
|
}
|
|
else
|
|
{
|
|
BonusOrientationGravitySharpness = 0.01f;
|
|
Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, Motor.Velocity.normalized,
|
|
1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime *
|
|
BonusOrientationGravitySharpness));
|
|
// Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, -Gravity.normalized,
|
|
// 1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime * BonusOrientationGravitySharpness));
|
|
currentRotation = Quaternion.FromToRotation(currentUp, smoothedGravityDir) *
|
|
currentRotation;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, Vector3.up,
|
|
1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime));
|
|
currentRotation = Quaternion.FromToRotation(currentUp, smoothedGravityDir) * currentRotation;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// (Called by KinematicCharacterMotor during its update cycle)
|
|
/// This is where you tell your character what its velocity should be right now.
|
|
/// This is the ONLY place where you can set the character's velocity
|
|
/// </summary>
|
|
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
|
{
|
|
// Ground movement
|
|
if (Motor.GroundingStatus.IsStableOnGround)
|
|
{
|
|
float currentVelocityMagnitude = currentVelocity.magnitude;
|
|
|
|
Vector3 effectiveGroundNormal = Motor.GroundingStatus.GroundNormal;
|
|
|
|
// Reorient velocity on slope
|
|
currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, effectiveGroundNormal) *
|
|
currentVelocityMagnitude;
|
|
|
|
// Calculate target velocity
|
|
Vector3 inputRight = Vector3.Cross(_moveInputVector, Motor.CharacterUp);
|
|
|
|
Vector3 reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized *
|
|
_moveInputVector.magnitude;
|
|
|
|
Vector3 targetMovementVelocity;
|
|
|
|
targetMovementVelocity = reorientedInput * MaxStableMoveSpeed;
|
|
|
|
// Smooth movement Velocity
|
|
currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity,
|
|
1f - Mathf.Exp(-StableMovementSharpness * deltaTime));
|
|
}
|
|
// Air movement
|
|
else
|
|
{
|
|
// Add move input
|
|
if (_moveInputVector.sqrMagnitude > 0f)
|
|
{
|
|
Vector3 addedVelocity = _moveInputVector * (AirAccelerationSpeed * deltaTime);
|
|
|
|
Vector3 currentVelocityOnInputsPlane =
|
|
Vector3.ProjectOnPlane(currentVelocity, Motor.CharacterUp);
|
|
|
|
// Limit air velocity from inputs
|
|
if (currentVelocityOnInputsPlane.magnitude < MaxAirMoveSpeed)
|
|
{
|
|
// clamp addedVel to make total vel not exceed max vel on inputs plane
|
|
Vector3 newTotal = Vector3.ClampMagnitude(currentVelocityOnInputsPlane + addedVelocity,
|
|
MaxAirMoveSpeed);
|
|
addedVelocity = newTotal - currentVelocityOnInputsPlane;
|
|
}
|
|
else
|
|
{
|
|
// Make sure added vel doesn't go in the direction of the already-exceeding velocity
|
|
if (Vector3.Dot(currentVelocityOnInputsPlane, addedVelocity) > 0f)
|
|
{
|
|
addedVelocity = Vector3.ProjectOnPlane(addedVelocity,
|
|
currentVelocityOnInputsPlane.normalized);
|
|
}
|
|
}
|
|
|
|
// Prevent air-climbing sloped walls
|
|
if (Motor.GroundingStatus.FoundAnyGround)
|
|
{
|
|
if (Vector3.Dot(currentVelocity + addedVelocity, addedVelocity) > 0f)
|
|
{
|
|
Vector3 perpenticularObstructionNormal = Vector3
|
|
.Cross(Vector3.Cross(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal),
|
|
Motor.CharacterUp).normalized;
|
|
addedVelocity =
|
|
Vector3.ProjectOnPlane(addedVelocity, perpenticularObstructionNormal);
|
|
}
|
|
}
|
|
|
|
// Apply added velocity
|
|
currentVelocity += addedVelocity;
|
|
}
|
|
|
|
// Gravity
|
|
currentVelocity += Gravity * deltaTime;
|
|
|
|
// Drag
|
|
currentVelocity *= (1f / (1f + (Drag * deltaTime)));
|
|
}
|
|
|
|
// Take into account additive velocity
|
|
if (_internalVelocityAdd.sqrMagnitude > 0f)
|
|
{
|
|
currentVelocity += _internalVelocityAdd;
|
|
_internalVelocityAdd = Vector3.zero;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// (Called by KinematicCharacterMotor during its update cycle)
|
|
/// This is called after the character has finished its movement update
|
|
/// </summary>
|
|
public void AfterCharacterUpdate(float deltaTime)
|
|
{
|
|
}
|
|
|
|
public void PostGroundingUpdate(float deltaTime)
|
|
{
|
|
// Handle landing and leaving ground
|
|
if (Motor.GroundingStatus.IsStableOnGround && !Motor.LastGroundingStatus.IsStableOnGround)
|
|
{
|
|
OnLanded();
|
|
}
|
|
else if (!Motor.GroundingStatus.IsStableOnGround && Motor.LastGroundingStatus.IsStableOnGround)
|
|
{
|
|
OnLeaveStableGround();
|
|
}
|
|
}
|
|
|
|
public bool IsColliderValidForCollisions(Collider coll)
|
|
{
|
|
if (IgnoredColliders.Count == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (IgnoredColliders.Contains(coll))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
|
|
ref HitStabilityReport hitStabilityReport)
|
|
{
|
|
}
|
|
|
|
public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
|
|
ref HitStabilityReport hitStabilityReport)
|
|
{
|
|
}
|
|
|
|
public void AddVelocity(Vector3 velocity)
|
|
{
|
|
_internalVelocityAdd += velocity;
|
|
}
|
|
|
|
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
|
|
Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
|
|
{
|
|
}
|
|
|
|
protected void OnLanded()
|
|
{
|
|
}
|
|
|
|
protected void OnLeaveStableGround()
|
|
{
|
|
}
|
|
|
|
public void OnDiscreteCollisionDetected(Collider hitCollider)
|
|
{
|
|
}
|
|
}
|
|
} |