Last active
July 13, 2025 16:04
-
-
Save Kellojo/5482771ba2a5052a43eb86458267cd5a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using KinematicCharacterController; | |
using UnityEngine; | |
using UnityEngine.Events; | |
// Requires https://assetstore.unity.com/packages/tools/physics/kinematic-character-controller-99131?srsltid=AfmBOoo58w1Y72hrXmLezY_KlLuoDZaYktGEHg0t2no4Mjzz6900IHtK | |
[RequireComponent(typeof(KinematicCharacterMotor))] | |
public class CharacterController : MonoBehaviour, ICharacterController { | |
[SerializeField] private float MovementSpeed = 9f; | |
[SerializeField] private float MovementResponsiveness = 20f; | |
[SerializeField] private float Gravity = -20f; | |
[SerializeField] private float JumpForce = 8f; | |
[SerializeField, Range(0, 1)] private float jumpHeldGravityMultiplier = 0.8f; | |
[SerializeField] private float CoyoteTime = 0.1f; | |
[SerializeField] private float airSpeed = 9f; | |
[SerializeField] private float airAcceleration = 60f; | |
[SerializeField] private Transform CameraTarget; | |
private KinematicCharacterMotor _motor; | |
[SerializeField] private Vector3 _requestedRotation; | |
[SerializeField] private Vector3 _requestedMovement; | |
[SerializeField] private bool _requestedJump; | |
[SerializeField] private bool _requestedJumpHelp; | |
[SerializeField] private Vector3 _requestedExternalForce; | |
[SerializeField] private float _timeSinceJumpRequested; | |
[SerializeField] private float _timeSinceUngrounded; | |
[SerializeField] private bool _ungroundedDueToJump; | |
/// <summary> | |
/// The character's current velocity caused by it's own movement | |
/// </summary> | |
public Vector3 CurrentOwnMoveVelocity => _motor.BaseVelocity; | |
/// <summary> | |
/// Includes external velocity such as velocity from moving platforms, ... | |
/// </summary> | |
public Vector3 CurrentOverallVelocity => _motor.Velocity; | |
public bool IsAirborne => !_motor.GroundingStatus.IsStableOnGround; | |
public float TurnSpeed => _requestedRotation.y; | |
public UnityEvent OnJump; | |
public UnityEvent OnLand; | |
private void Awake() { | |
Cursor.lockState = CursorLockMode.Locked; | |
_requestedRotation = transform.eulerAngles; | |
_motor = GetComponent<KinematicCharacterMotor>(); | |
_motor.CharacterController = this; | |
} | |
private void Update() { | |
CameraTarget.localRotation = Quaternion.Euler(_requestedRotation.x, 0, 0); | |
_requestedRotation.x = CameraTarget.localRotation.eulerAngles.x; | |
} | |
public void UpdateInput(CharacterInputData input) { | |
_requestedRotation += new Vector3(-input.Rotation.y, input.Rotation.x, 0); | |
_requestedMovement = new Vector3(input.Movement.x, 0, input.Movement.z); | |
_requestedMovement = Vector3.ClampMagnitude(_requestedMovement, 1f); | |
_requestedMovement = CameraTarget.rotation * _requestedMovement; | |
var wasRequestingJump = _requestedJump; | |
_requestedJump = input.Jump || _requestedJump; | |
if (_requestedJump && !wasRequestingJump) { | |
_timeSinceJumpRequested = 0f; | |
} | |
_requestedJumpHelp = input.JumpHelp; | |
} | |
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) { | |
if (_requestedExternalForce.sqrMagnitude > 0) { | |
currentVelocity = _requestedExternalForce; | |
_requestedExternalForce = Vector3.zero; | |
_motor.ForceUnground(0); | |
} else if (_motor.GroundingStatus.IsStableOnGround) { | |
if (_ungroundedDueToJump) OnLand?.Invoke(); | |
_timeSinceUngrounded = 0f; | |
_ungroundedDueToJump = false; | |
var groundedMovement = _motor.GetDirectionTangentToSurface(_requestedMovement, _motor.GroundingStatus.GroundNormal) * _requestedMovement.magnitude; | |
var targetVelocity = groundedMovement * MovementSpeed; | |
currentVelocity = Vector3.Lerp(currentVelocity, targetVelocity, 1 - Mathf.Exp(-MovementResponsiveness * deltaTime)); | |
} else { | |
_timeSinceUngrounded += deltaTime; | |
if (_requestedMovement.sqrMagnitude > 0) { | |
var planarMovement = Vector3.ProjectOnPlane(_requestedMovement, _motor.CharacterUp).normalized * _requestedMovement.magnitude; | |
var currentPlanarVelocity = Vector3.ProjectOnPlane(currentVelocity, _motor.CharacterUp); | |
var movementForce = planarMovement * (airAcceleration * deltaTime); | |
var targetPlanarVelocity = currentPlanarVelocity + movementForce; | |
targetPlanarVelocity = Vector3.ClampMagnitude(targetPlanarVelocity, airSpeed); | |
currentVelocity += targetPlanarVelocity - currentPlanarVelocity; | |
} | |
var gravity = Gravity; | |
var verticalVelocity = Vector3.Dot(currentVelocity, _motor.CharacterUp); | |
if (_requestedJumpHelp && verticalVelocity > 0f) { | |
gravity *= jumpHeldGravityMultiplier; | |
} | |
currentVelocity += _motor.CharacterUp * (gravity * deltaTime); | |
} | |
if (_requestedJump) { | |
var grounded = _motor.GroundingStatus.IsStableOnGround; | |
var canCoyoteJump = _timeSinceUngrounded < CoyoteTime && !_ungroundedDueToJump; | |
if (grounded || canCoyoteJump) { | |
_requestedJump = false; | |
_motor.ForceUnground(0); | |
_ungroundedDueToJump = true; | |
var currentVerticalVelocity = Vector3.Dot(currentVelocity, _motor.CharacterUp); | |
var targetVerticalVelocity = Mathf.Max(currentVerticalVelocity, JumpForce); | |
currentVelocity += _motor.CharacterUp * (targetVerticalVelocity - currentVerticalVelocity); | |
OnJump?.Invoke(); | |
} else { | |
_timeSinceJumpRequested += deltaTime; | |
var canJumpLater = _timeSinceJumpRequested < CoyoteTime; | |
_requestedJump = canJumpLater; | |
} | |
} | |
} | |
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime) { | |
currentRotation = Quaternion.Euler(0, _requestedRotation.y, 0); | |
_requestedRotation = currentRotation.eulerAngles; | |
_requestedRotation.x = CameraTarget.localRotation.eulerAngles.x; | |
} | |
public void BeforeCharacterUpdate(float deltaTime) { | |
} | |
public void PostGroundingUpdate(float deltaTime) { | |
} | |
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 ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport) { | |
} | |
public void OnDiscreteCollisionDetected(Collider hitCollider) { | |
} | |
public void TeleportTo(Vector3 position) { | |
_motor.SetPosition(position); | |
} | |
public void ApplyExternalForce(Vector3 velocity) { | |
_requestedExternalForce = velocity; | |
} | |
} | |
public struct CharacterInputData { | |
public Vector2 Rotation; | |
public Vector3 Movement; | |
public bool Jump; | |
public bool JumpHelp; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine; | |
public class CharacterInput : MonoBehaviour | |
{ | |
[SerializeField] private CharacterController _controller; | |
private void Update() { | |
var input = new CharacterInputData { | |
Rotation = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y")), | |
Movement = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")), | |
Jump = Input.GetButtonDown("Jump"), | |
JumpHelp = Input.GetButton("Jump") | |
}; | |
_controller.UpdateInput(input); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment