using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using System;
namespace KinematicCharacterController.Walkthrough.FramePerfectRotation
{
public struct PlayerCharacterInputs
{
public float MoveAxisForward;
public float MoveAxisRight;
public Quaternion CameraRotation;
}
public class MyCharacterController : MonoBehaviour, ICharacterController
{
public KinematicCharacterMotor Motor;
[Header("Stable Movement")]
public float MaxStableMoveSpeed = 10f;
public float StableMovementSharpness = 15;
public float OrientationSharpness = 10;
[Header("Air Movement")]
public float MaxAirMoveSpeed = 10f;
public float AirAccelerationSpeed = 5f;
public float Drag = 0.1f;
[Header("Misc")]
public Vector3 Gravity = new Vector3(0, -30f, 0);
public Transform MeshRoot;
public bool FramePerfectRotation = true;
private Vector3 _moveInputVector;
private Vector3 _lookInputVector;
private void Start()
{
// Assign to motor
Motor.CharacterController = this;
}
///
/// This is called every frame by MyPlayer in order to tell the character what its inputs are
///
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;
_lookInputVector = cameraPlanarDirection;
}
public void PostInputUpdate(float deltaTime, Vector3 cameraForward)
{
if (FramePerfectRotation)
{
_lookInputVector = Vector3.ProjectOnPlane(cameraForward, Motor.CharacterUp);
Quaternion newRotation = default;
HandleRotation(ref newRotation, deltaTime);
MeshRoot.rotation = newRotation;
}
}
private void HandleRotation(ref Quaternion rot, float deltaTime)
{
if (_lookInputVector != Vector3.zero)
{
rot = Quaternion.LookRotation(_lookInputVector, Motor.CharacterUp);
}
}
///
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called before the character begins its movement update
///
public void BeforeCharacterUpdate(float deltaTime)
{
}
///
/// (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
///
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
{
HandleRotation(ref currentRotation, deltaTime);
}
///
/// (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
///
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{
Vector3 targetMovementVelocity = Vector3.zero;
if (Motor.GroundingStatus.IsStableOnGround)
{
// Reorient velocity on slope
currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, Motor.GroundingStatus.GroundNormal) * currentVelocity.magnitude;
// Calculate target velocity
Vector3 inputRight = Vector3.Cross(_moveInputVector, Motor.CharacterUp);
Vector3 reorientedInput = Vector3.Cross(Motor.GroundingStatus.GroundNormal, inputRight).normalized * _moveInputVector.magnitude;
targetMovementVelocity = reorientedInput * MaxStableMoveSpeed;
// Smooth movement Velocity
currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1 - Mathf.Exp(-StableMovementSharpness * deltaTime));
}
else
{
// Add move input
if (_moveInputVector.sqrMagnitude > 0f)
{
targetMovementVelocity = _moveInputVector * MaxAirMoveSpeed;
// Prevent climbing on un-stable slopes with air movement
if (Motor.GroundingStatus.FoundAnyGround)
{
Vector3 perpenticularObstructionNormal = Vector3.Cross(Vector3.Cross(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal), Motor.CharacterUp).normalized;
targetMovementVelocity = Vector3.ProjectOnPlane(targetMovementVelocity, perpenticularObstructionNormal);
}
Vector3 velocityDiff = Vector3.ProjectOnPlane(targetMovementVelocity - currentVelocity, Gravity);
currentVelocity += velocityDiff * AirAccelerationSpeed * deltaTime;
}
// Gravity
currentVelocity += Gravity * deltaTime;
// Drag
currentVelocity *= (1f / (1f + (Drag * deltaTime)));
}
}
///
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called after the character has finished its movement update
///
public void AfterCharacterUpdate(float deltaTime)
{
}
public bool IsColliderValidForCollisions(Collider coll)
{
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 PostGroundingUpdate(float deltaTime)
{
}
public void AddVelocity(Vector3 velocity)
{
}
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
{
}
public void OnDiscreteCollisionDetected(Collider hitCollider)
{
}
}
}