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 float SpeedMultiplier = 1; 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("Allow position movement.")] public bool AllowMove = true; [Tooltip("Allow looking around.")] public bool AllowLook = true; [Tooltip("Allow run.")] public bool AllowRun = true; [Tooltip("Allow jump.")] public bool AllowJump = true; [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("Gamepad horizontal sensitivity.")] public float GamepadSensitivityX = 150f; [Tooltip("Gamepad vertical sensitivity.")] public float GamepadSensitivityY = 150f; [Tooltip("Minimum pitch (looking down).")] public float MinPitch = -80f; [Tooltip("Maximum pitch (looking up).")] public float MaxPitch = 80f; [Tooltip("Reference yaw.")] [Range(-180, 180)] public float ReferenceYaw = 0; [Tooltip("Range yaw.")] [Range(0, 360)] public float RangeYaw = 360; } [SerializeField, OctoReadOnly] private 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 StateMachine State => _state; 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 SetSppedMultiplier(float multiplier) { _state.SpeedMultiplier = multiplier; } public void AllowMove(bool allow) { _settings.AllowMove = allow; } 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 = _settings.AllowRun; } private void Jump() { _jumpInput = _settings.AllowJump; } 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 || !_settings.AllowMove) ? 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, 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) * _state.SpeedMultiplier; 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.enabled) _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 || !_settings.AllowLook) ? Vector2.zero : _lookInput; if (_settings.AllowLook) { bool isGamepad = Gamepad.current != null && Gamepad.current.rightStick.ReadValue().magnitude > 0.01f; float sensX = isGamepad ? _firstPerson.GamepadSensitivityX : _firstPerson.SensitivityX; float sensY = isGamepad ? _firstPerson.GamepadSensitivityY : _firstPerson.SensitivityY; float lookX = isGamepad ? delta.x * sensX * Time.deltaTime : delta.x * sensX; float lookY = isGamepad ? delta.y * sensY * -1 * Time.deltaTime : delta.y * sensY * -1; _state.Yaw += lookX; float halfRange = _firstPerson.RangeYaw * 0.5f; float deltaYaw = Mathf.DeltaAngle(_firstPerson.ReferenceYaw, _state.Yaw); deltaYaw = Mathf.Clamp(deltaYaw, -halfRange, halfRange); _state.Yaw = _firstPerson.ReferenceYaw + deltaYaw; _state.Pitch = Mathf.Clamp(_state.Pitch + lookY, _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); }*/ } }