using OctoCore.Inputs; using System.Globalization; using UnityEngine; using UnityEngine.InputSystem; 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] public class StateMachine { public bool IsFocussed; public bool IsPaused; public bool IsRunning; public float Yaw; public float Pitch; public OctoPlayerDriver Driver; } [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] public 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; } [OctoReadOnly] public StateMachine State; [SerializeField] private References _references; [SerializeField] private Settings _settings; [SerializeField] private Mapping _mapping; [SerializeField] private FirstPerson _firstPerson; private bool _wasPausedBeforeUnfocuss; private bool _jumpInput; private bool _jumpping; private bool _runInput; private float _runInertia; private float _jumpLerp; private float _jumpTime; private float _jumpAltitude; private Vector2 _moveInput; private Vector2 _lookInput; private Vector3 _moveInertia; private Vector3 _moveDirection; public bool IsPaused => State.IsPaused; public float Radius => _references.Controller ? _references.Controller.radius : 0; public FirstPerson _FirstPerson => _firstPerson; 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); State.Yaw = transform.eulerAngles.y; State.IsFocussed = Application.isFocused; Pause(State.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() { HandleFocus(); HandleMovement(); } public void Teleport(string positionAndYaw) { string[] s = positionAndYaw.Replace(" ", string.Empty).Split(','); float x = float.Parse(s[0], CultureInfo.InvariantCulture.NumberFormat); float y = float.Parse(s[1], CultureInfo.InvariantCulture.NumberFormat); float z = float.Parse(s[2], CultureInfo.InvariantCulture.NumberFormat); float a = float.Parse(s[3], CultureInfo.InvariantCulture.NumberFormat); Teleport(new Vector3(x, y, z), Quaternion.Euler(0, a, 0)); } public void Teleport(Vector3 position, Quaternion rotation) { State.IsPaused = _jumpInput = _jumpping = _runInput = false; _runInertia = _jumpLerp = _jumpTime = _jumpAltitude = 0; _moveInput = _lookInput = Vector2.zero; _moveInertia = _moveDirection = Vector3.zero; State.Yaw = rotation.eulerAngles.y; State.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) { State.IsPaused = pause; if (OctoCore.Instance) OctoCore.Instance.UI.SetCursor(); } public void Drive(OctoPlayerDriver driver) { State.Driver = driver; } public void ReleaseDrive() { State.Driver = null; } public void SetYaw(float yaw) { State.Yaw = yaw; } public void SetPitch(float pitch) { State.Yaw = pitch; } 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 HandleFocus() { bool escapePressed = false; bool mouseClicked = false; #if ENABLE_INPUT_SYSTEM escapePressed = Keyboard.current != null && Keyboard.current.escapeKey.wasPressedThisFrame; mouseClicked = Mouse.current != null && ( Mouse.current.leftButton.wasPressedThisFrame || Mouse.current.rightButton.wasPressedThisFrame || Mouse.current.middleButton.wasPressedThisFrame || Mouse.current.forwardButton.wasPressedThisFrame || Mouse.current.backButton.wasPressedThisFrame ); #else escapePressed = Input.GetKeyDown(KeyCode.Escape); mouseClicked = Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1) || Input.GetMouseButtonDown(2); #endif if (!Application.isFocused || escapePressed) { if (State.IsFocussed) { State.IsFocussed = false; _wasPausedBeforeUnfocuss = State.IsPaused; Pause(true); } } else if (!State.IsFocussed && mouseClicked) { State.IsFocussed = true; if (!_wasPausedBeforeUnfocuss) Pause(false); } } private void HandleMovement() { _moveInput = (State.IsPaused || !State.IsFocussed) ? 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; } if (State.Driver) State.Driver.ApplyMoveInfluence(move, move.magnitude * t, out move); 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)); State.IsRunning = _runInertia > .1f; 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) { _jumpInput = false; _jumpLerp = (Time.timeSinceLevelLoad - _jumpTime) / _settings.JumpDuration; if (_jumpLerp >= 1 || (_jumpLerp > .3f && isGrounded)) { _jumpping = false; _jumpLerp = 0; } else { float x = _jumpLerp; x = x * 2 - 1; 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 && !State.IsPaused) { Vector2 delta = (State.IsPaused || !State.IsFocussed) ? Vector2.zero : _lookInput; float mouseX = delta.x * _firstPerson.SensitivityX; float mouseY = delta.y * _firstPerson.SensitivityY * -1; State.Yaw += mouseX; State.Pitch = Mathf.Clamp(State.Pitch + mouseY, _firstPerson.MinPitch, _firstPerson.MaxPitch); if (State.Driver) State.Driver.ApplyLookInfluence(State.Yaw, State.Pitch, Time.deltaTime, out State.Yaw, out State.Pitch); Quaternion YawRot = Quaternion.Euler(0, State.Yaw, 0); Quaternion PitchRot = Quaternion.Euler(State.Pitch, 0, 0); transform.rotation = YawRot; cam.transform.localRotation = PitchRot; } 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; } 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); }*/ } }