using OctoCore.Inputs; using UnityEngine; namespace OctoCore.Components { public class OctoPlayer : MonoBehaviour { public static OctoPlayer Instance { get; private set; } public enum PlayerModes { FirstPerson, ThirdPerson } public enum Orientations { World, Camera } [System.Serializable] private class References { public CharacterController Controller; } [System.Serializable] private class Settings { public PlayerModes Mode = PlayerModes.FirstPerson; public Orientations Orientation = Orientations.Camera; [Tooltip("Walk speed (km/h).")] public float WalkSpeed = 10; [Tooltip("Run speed (km/h).")] public float RunSpeed = 20; [Tooltip("Jump duration (seconds).")] public float JumpDuration = .4f; [Tooltip("Maximum jump height reached at the apex (meters).")] public float JumpHeight = .4f; [Tooltip("Downward acceleration applied to the player (m/s²).")] public float Gravity = 9.8f; } [System.Serializable] private class Mapping { public OctoActions MoveAction; public OctoActions LookAction; public OctoActions RunAction; public OctoActions JumpAction; } [System.Serializable] private class FirstPerson { [Header("References")] public Camera Camera; [Header("Settings")] [Tooltip("Horizontal sensitivity (Yaw).")] public float SensitivityX = 0.1f; [Tooltip("Vertical sensitivity (Pitch).")] public float SensitivityY = 0.1f; [Tooltip("Minimum pitch (looking down).")] public float MinPitch = -80f; [Tooltip("Maximum pitch (looking up).")] public float MaxPitch = 80f; } [SerializeField] private References _references; [SerializeField] private Settings _settings; [SerializeField] private Mapping _mapping; [SerializeField] private FirstPerson _firstPerson; private bool _isPaused; private bool _jumpInput; private bool _jumpping; private bool _runInput; private float _runInertia; private float _yaw; private float _pitch; private float _jumpLerp; private float _jumpTime; private float _jumpAltitude; private Vector2 _moveInput; private Vector2 _lookInput; private Vector3 _moveInertia; private Vector3 _moveDirection; public bool IsPaused => _isPaused; public float Radius => _references.Controller ? _references.Controller.radius : 0; // Public access to yaw and pitch for external aim assist systems public float Yaw { get => _yaw; set => _yaw = value; } public float Pitch { get => _pitch; set => _pitch = Mathf.Clamp(value, _firstPerson.MinPitch, _firstPerson.MaxPitch); } void OnEnable() { if (!Instance) Instance = this; else Destroy(gameObject); OctoCore.Instance.Inputs.SubscribeAction(_mapping.MoveAction, Move); OctoCore.Instance.Inputs.SubscribeAction(_mapping.LookAction, Look); OctoCore.Instance.Inputs.SubscribeAction(_mapping.RunAction, Run); OctoCore.Instance.Inputs.SubscribeAction(_mapping.JumpAction, Jump); _yaw = transform.eulerAngles.y; Pause(_isPaused); } void OnDisable() { OctoCore.Instance.Inputs.UnsubscribeAction(_mapping.MoveAction, Move); OctoCore.Instance.Inputs.UnsubscribeAction(_mapping.RunAction, Run); OctoCore.Instance.Inputs.UnsubscribeAction(_mapping.JumpAction, Jump); if (Instance == this) Instance = null; } void Update() { _moveInput = _isPaused ? Vector2.zero : Vector2.ClampMagnitude(_moveInput, 1); Camera cam = (_firstPerson.Camera && _firstPerson.Camera.enabled && _firstPerson.Camera.gameObject.activeInHierarchy) ? _firstPerson.Camera : Camera.main; bool accelerating = _moveInput.magnitude > .1f; bool isGrounded = _references.Controller ? _references.Controller.isGrounded : true; float t = Time.deltaTime / 3.6f; // Move Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y); if (_settings.Orientation == Orientations.Camera && cam) { Vector3 camForward = cam.transform.forward; camForward.y = 0; camForward.Normalize(); Vector3 camRight = cam.transform.right; camRight.y = 0; camRight.Normalize(); move = camRight * move.x + camForward * move.z; } float deltaSmooth = t * (accelerating ? 30 : 15); _moveDirection = Vector3.Lerp(_moveDirection, move, deltaSmooth * 1.5f); deltaSmooth *= 1 - Vector3.Cross(_moveInertia, _moveDirection).magnitude; _moveInertia = Vector3.Lerp(_moveInertia, move, deltaSmooth); if (!accelerating && _moveInertia.magnitude < .01f) _moveInertia = Vector3.zero; _runInertia = Mathf.Lerp(_runInertia, _runInput ? 1 : 0, t * (accelerating ? 5 : 3)); Vector3 moveDelta = _moveInertia * t * Mathf.Lerp(_settings.WalkSpeed, _settings.RunSpeed, _runInertia); if (isGrounded && _jumpInput && !_jumpping) { _jumpInput = false; _jumpping = true; _jumpLerp = 0; _jumpTime = Time.timeSinceLevelLoad; _jumpAltitude = transform.position.y; } float jumpOffset = 0; if (_jumpping) { _jumpLerp = (Time.timeSinceLevelLoad - _jumpTime) / _settings.JumpDuration; if (_jumpLerp >= 1 || (_jumpLerp > .3f && isGrounded)) { _jumpping = false; _jumpLerp = 0; } else { float x = _jumpLerp; x = x * 2 - 1f; float y; if (x < 0) y = Mathf.Sqrt(1f - x * x); else y = Mathf.Cos(x * .5f * Mathf.PI); y = _jumpAltitude + y * _settings.JumpHeight; jumpOffset = y - transform.position.y; } } if (_jumpping) moveDelta.y = jumpOffset; else moveDelta.y -= _settings.Gravity * Time.deltaTime; if (_references.Controller) _references.Controller.Move(moveDelta); else transform.position += moveDelta; // Rotate switch (_settings.Mode) { case PlayerModes.FirstPerson: if (cam && !_isPaused) { Vector2 delta = _lookInput; float mouseX = delta.x * _firstPerson.SensitivityX; float mouseY = delta.y * _firstPerson.SensitivityY * -1; _yaw += mouseX; _pitch = Mathf.Clamp(_pitch + mouseY, _firstPerson.MinPitch, _firstPerson.MaxPitch); transform.rotation = Quaternion.Euler(0, _yaw, 0); cam.transform.localRotation = Quaternion.Euler(_pitch, 0, 0); } break; case PlayerModes.ThirdPerson: { Quaternion targetRot = transform.rotation; if (_moveInertia.magnitude > .002f) { Vector3 offset = _moveInertia - _moveDirection; offset *= _moveInertia.magnitude * _runInertia; offset = Vector3.ClampMagnitude(offset * 1f, 1f); Vector3 up = (Vector3.up - offset).normalized; Quaternion lookRot = Quaternion.LookRotation(_moveInertia, up); targetRot = Quaternion.Lerp(targetRot, lookRot, t * 30); } transform.rotation = targetRot; break; } } // Reset _moveInput = Vector2.zero; _lookInput = Vector2.zero; _runInput = false; } public void Teleport(Vector3 position, Quaternion rotation) { _isPaused = _jumpInput = _jumpping = _runInput = false; _runInertia = _jumpLerp = _jumpTime = _jumpAltitude = 0; _moveInput = _lookInput = Vector2.zero; _moveInertia = _moveDirection = Vector3.zero; _yaw = rotation.eulerAngles.y; _pitch = 0; if (_references.Controller) _references.Controller.enabled = false; transform.position = position; transform.rotation = rotation; if (_references.Controller) _references.Controller.enabled = true; } public void Pause(bool pause) { _isPaused = pause; if (OctoCore.Instance) OctoCore.Instance.UI.SetCursor(); } private void Move(Vector2 value) { _moveInput = value; } private void Look(Vector2 value) { _lookInput = value; } private void Run() { _runInput = true; } private void Jump() { _jumpInput = true; } private void SetMode() { switch (_settings.Mode) { case PlayerModes.FirstPerson: // Activate First Person Camera break; case PlayerModes.ThirdPerson: // Activate Third Person Camera break; } } /*public static float Arc(float x) { x = Mathf.Clamp01(x) * 2 - 1f; return Mathf.Sqrt(1f - x * x); }*/ } }