Created
April 20, 2026 02:59
-
-
Save devzl/4a13bda9f39f5621ab06c8165b0b55e0 to your computer and use it in GitHub Desktop.
HackyCamera to zoom in and pitch with Third Person Controller
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
| namespace DustboundD.Gameplay.Camera | |
| { | |
| using System.Collections.Generic; | |
| using System.Collections; | |
| using System.Reflection; | |
| using Opsive.Shared.Input; | |
| using Opsive.Shared.Utility; | |
| using Opsive.UltimateCharacterController.Camera; | |
| using Opsive.UltimateCharacterController.Character; | |
| using UnityEngine; | |
| #if ENABLE_INPUT_SYSTEM | |
| using UnityEngine.InputSystem; | |
| #endif | |
| using TopDownView = Opsive.UltimateCharacterController.ThirdPersonController.Camera.ViewTypes.TopDown; | |
| using AdventureMovementType = Opsive.UltimateCharacterController.ThirdPersonController.Character.MovementTypes.Adventure; | |
| using TopDownMovementType = Opsive.UltimateCharacterController.ThirdPersonController.Character.MovementTypes.TopDown; | |
| [DisallowMultipleComponent] | |
| public class TopDownCameraHackController : MonoBehaviour | |
| { | |
| [Header("Zoom")] | |
| [SerializeField] private float m_MinViewDistance = 10f; | |
| [SerializeField] private float m_MaxViewDistance = 30f; | |
| [SerializeField] private float m_MouseWheelZoomStep = 3f; | |
| [SerializeField] private KeyCode m_MouseWheelZoomModifierKey = KeyCode.V; | |
| [SerializeField] private float m_KeyboardZoomSpeed = 14f; | |
| [SerializeField] private float m_ControllerZoomSpeed = 18f; | |
| [SerializeField] private float m_ZoomSmoothSpeed = 10f; | |
| [Header("Pitch")] | |
| [SerializeField] private float m_ZoomedInPitch = 56f; | |
| [SerializeField] private float m_ZoomedOutPitch = 78f; | |
| [SerializeField] private float m_PitchSmoothSpeed = 8f; | |
| [SerializeField] private float m_MaxPitchLimit = 89f; | |
| [Header("Aim")] | |
| [SerializeField] private bool m_SwitchToTopDownMovementWhileAiming = true; | |
| [SerializeField] private string m_AimInputName = "Fire2"; | |
| [SerializeField] private bool m_EnableControllerAimMode; | |
| [SerializeField] private float m_ControllerAimThreshold = 0.2f; | |
| [SerializeField] private float m_ControllerRotateSpeed = 180f; | |
| [SerializeField] private bool m_UseSmoothedMouseAimRotation = true; | |
| [SerializeField] private float m_MouseAimRotateSpeed = 720f; | |
| [SerializeField] private float m_MinMouseAimDistance = 0.35f; | |
| [SerializeField] private bool m_ForceTopDownRelativeCameraMovement = true; | |
| [SerializeField] private bool m_ForceTopDownLookAtCursor = true; | |
| [Header("Discovery")] | |
| [SerializeField] private bool m_ForceTopDownViewIfFound; | |
| [SerializeField] private bool m_DisableBuiltInRightClickZoom = true; | |
| [SerializeField] private bool m_KeepMouseVisible = true; | |
| [Header("Debug")] | |
| [SerializeField] private bool m_ShowDebugGui = true; | |
| [SerializeField] private float m_DebugTriggerPressedThreshold = 0.5f; | |
| private CameraController m_CameraController; | |
| private CameraControllerHandler m_CameraControllerHandler; | |
| private TopDownView m_TopDownView; | |
| private TopDownMovementType m_TopDownMovementType; | |
| private UltimateCharacterLocomotion m_CharacterLocomotion; | |
| private IPlayerInput m_PlayerInput; | |
| private string m_AdventureMovementTypeFullName; | |
| private string m_TopDownMovementTypeFullName; | |
| private string m_OriginalZoomInputName; | |
| private float m_TargetViewDistance; | |
| private float m_CurrentPitchMin; | |
| private float m_PitchRange = 10f; | |
| private bool m_AimMovementActive; | |
| private bool m_Configured; | |
| private CursorLockMode m_PreviousCursorLockMode; | |
| private bool m_PreviousCursorVisible; | |
| private bool m_CursorStateCached; | |
| private readonly List<Component> m_CursorManagedComponents = new List<Component>(); | |
| private readonly List<bool> m_PreviousDisableCursorValues = new List<bool>(); | |
| private readonly List<bool> m_PreviousEnableCursorWithEscapeValues = new List<bool>(); | |
| private static readonly BindingFlags c_InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; | |
| private static readonly string c_AdventureMovementTypeFullName = typeof(AdventureMovementType).FullName; | |
| private static readonly string c_TopDownMovementTypeFullName = typeof(TopDownMovementType).FullName; | |
| private GUIStyle m_DebugLabelStyle; | |
| private GUIStyle m_DebugBoxStyle; | |
| private Coroutine m_CursorEnforcementCoroutine; | |
| private void Awake() | |
| { | |
| TryInitializeDefaultMovement(forceRefresh: true); | |
| } | |
| private void OnEnable() | |
| { | |
| TryInitializeDefaultMovement(forceRefresh: true); | |
| if (m_CursorEnforcementCoroutine == null) { | |
| m_CursorEnforcementCoroutine = StartCoroutine(EnforceCursorVisibilityAtEndOfFrame()); | |
| } | |
| } | |
| private void Start() | |
| { | |
| TryInitializeDefaultMovement(forceRefresh: true); | |
| } | |
| private void Update() | |
| { | |
| if (!ResolveReferences(forceRefresh: false)) { | |
| return; | |
| } | |
| UpdateZoom(); | |
| UpdateMovementMode(); | |
| UpdateMouseAimRotation(); | |
| UpdateControllerRotation(); | |
| MaintainMouseVisibility(); | |
| } | |
| private void LateUpdate() | |
| { | |
| if (!ResolveReferences(forceRefresh: false)) { | |
| return; | |
| } | |
| if (!IsAimActiveThisFrame()) { | |
| RestoreDefaultMovementType(); | |
| } | |
| } | |
| private void OnDisable() | |
| { | |
| if (m_CursorEnforcementCoroutine != null) { | |
| StopCoroutine(m_CursorEnforcementCoroutine); | |
| m_CursorEnforcementCoroutine = null; | |
| } | |
| RestoreZoomInputName(); | |
| RestoreDefaultMovementType(); | |
| RestoreCursorConfiguration(); | |
| } | |
| private void OnGUI() | |
| { | |
| if (!m_ShowDebugGui) { | |
| return; | |
| } | |
| EnsureDebugGuiStyles(); | |
| var debugLines = BuildDebugLines(); | |
| var lineHeight = m_DebugLabelStyle.lineHeight + 2f; | |
| var width = 280f; | |
| var height = 12f + (debugLines.Count * lineHeight); | |
| var rect = new Rect(Screen.width - width - 16f, 16f, width, height); | |
| GUI.Box(rect, GUIContent.none, m_DebugBoxStyle); | |
| var contentRect = new Rect(rect.x + 10f, rect.y + 8f, rect.width - 20f, rect.height - 16f); | |
| for (int i = 0; i < debugLines.Count; ++i) { | |
| var lineRect = new Rect(contentRect.x, contentRect.y + (i * lineHeight), contentRect.width, lineHeight); | |
| GUI.Label(lineRect, debugLines[i], m_DebugLabelStyle); | |
| } | |
| } | |
| private void TryInitializeDefaultMovement(bool forceRefresh) | |
| { | |
| if (!ResolveReferences(forceRefresh)) { | |
| return; | |
| } | |
| RestoreDefaultMovementType(); | |
| } | |
| private bool ResolveReferences(bool forceRefresh) | |
| { | |
| if (!forceRefresh && m_CameraController != null && m_TopDownView != null && m_CharacterLocomotion != null) { | |
| return true; | |
| } | |
| m_CameraController = FindActiveCameraController(); | |
| if (m_CameraController == null) { | |
| return false; | |
| } | |
| m_CameraControllerHandler = m_CameraController.GetComponent<CameraControllerHandler>(); | |
| m_CharacterLocomotion = FindPlayerCharacterLocomotion(); | |
| if (m_CharacterLocomotion == null) { | |
| m_CharacterLocomotion = m_CameraController.CharacterLocomotion; | |
| } | |
| if (m_CharacterLocomotion == null && m_CameraController.Character != null) { | |
| m_CharacterLocomotion = m_CameraController.Character.GetComponent<UltimateCharacterLocomotion>(); | |
| } | |
| if (m_CharacterLocomotion == null) { | |
| return false; | |
| } | |
| m_PlayerInput = m_CharacterLocomotion.GetComponent<IPlayerInput>(); | |
| m_TopDownView = FindTopDownView(m_CameraController); | |
| if (m_TopDownView == null) { | |
| return false; | |
| } | |
| if (m_ForceTopDownViewIfFound && !(m_CameraController.ActiveViewType is TopDownView)) { | |
| m_CameraController.SetViewType(typeof(TopDownView), false); | |
| } | |
| CacheMovementTypeNames(); | |
| CacheMovementTypeInstances(); | |
| ConfigureInitialZoomState(); | |
| DisableBuiltInRightClickZoom(); | |
| ConfigureCursorMode(); | |
| RestoreDefaultMovementType(); | |
| m_Configured = true; | |
| return true; | |
| } | |
| private CameraController FindActiveCameraController() | |
| { | |
| var mainCamera = Camera.main; | |
| if (mainCamera != null) { | |
| var mainCameraController = mainCamera.GetComponentInParent<CameraController>(); | |
| if (mainCameraController != null && mainCameraController.isActiveAndEnabled) { | |
| return mainCameraController; | |
| } | |
| } | |
| var cameraControllers = FindObjectsByType<CameraController>(FindObjectsInactive.Exclude, FindObjectsSortMode.None); | |
| for (int i = 0; i < cameraControllers.Length; ++i) { | |
| var cameraController = cameraControllers[i]; | |
| var cameraComponent = cameraController.GetComponent<Camera>(); | |
| if (cameraComponent != null && cameraComponent.isActiveAndEnabled) { | |
| return cameraController; | |
| } | |
| } | |
| return cameraControllers.Length > 0 ? cameraControllers[0] : null; | |
| } | |
| private UltimateCharacterLocomotion FindPlayerCharacterLocomotion() | |
| { | |
| GameObject player = null; | |
| try { | |
| player = GameObject.FindGameObjectWithTag("Player"); | |
| } catch (UnityException) { | |
| return null; | |
| } | |
| if (player == null) { | |
| return null; | |
| } | |
| var locomotion = player.GetComponent<UltimateCharacterLocomotion>(); | |
| if (locomotion != null) { | |
| return locomotion; | |
| } | |
| locomotion = player.GetComponentInChildren<UltimateCharacterLocomotion>(); | |
| if (locomotion != null) { | |
| return locomotion; | |
| } | |
| return player.GetComponentInParent<UltimateCharacterLocomotion>(); | |
| } | |
| private TopDownView FindTopDownView(CameraController cameraController) | |
| { | |
| if (cameraController.ActiveViewType is TopDownView activeTopDown) { | |
| return activeTopDown; | |
| } | |
| var viewTypes = cameraController.ViewTypes; | |
| if (viewTypes == null) { | |
| return null; | |
| } | |
| for (int i = 0; i < viewTypes.Length; ++i) { | |
| if (viewTypes[i] is TopDownView topDown) { | |
| return topDown; | |
| } | |
| } | |
| return null; | |
| } | |
| private void CacheMovementTypeNames() | |
| { | |
| if (!string.IsNullOrEmpty(m_AdventureMovementTypeFullName) && !string.IsNullOrEmpty(m_TopDownMovementTypeFullName)) { | |
| return; | |
| } | |
| var movementTypes = m_CharacterLocomotion.MovementTypes; | |
| for (int i = 0; i < movementTypes.Length; ++i) { | |
| if (string.IsNullOrEmpty(m_AdventureMovementTypeFullName) && movementTypes[i] is AdventureMovementType) { | |
| m_AdventureMovementTypeFullName = c_AdventureMovementTypeFullName; | |
| } | |
| if (movementTypes[i] is TopDownMovementType) { | |
| m_TopDownMovementTypeFullName = c_TopDownMovementTypeFullName; | |
| } | |
| } | |
| } | |
| private void CacheMovementTypeInstances() | |
| { | |
| if (m_TopDownMovementType != null) { | |
| return; | |
| } | |
| var movementTypes = m_CharacterLocomotion.MovementTypes; | |
| for (int i = 0; i < movementTypes.Length; ++i) { | |
| if (movementTypes[i] is TopDownMovementType topDownMovementType) { | |
| m_TopDownMovementType = topDownMovementType; | |
| break; | |
| } | |
| } | |
| ConfigureTopDownMovementType(); | |
| } | |
| private void ConfigureInitialZoomState() | |
| { | |
| if (m_Configured) { | |
| return; | |
| } | |
| m_TargetViewDistance = Mathf.Clamp(m_TopDownView.ViewDistance, m_MinViewDistance, m_MaxViewDistance); | |
| m_TopDownView.ViewDistance = m_TargetViewDistance; | |
| m_PitchRange = Mathf.Max(0f, m_TopDownView.PitchLimit.MaxValue - m_TopDownView.PitchLimit.MinValue); | |
| m_CurrentPitchMin = m_TopDownView.PitchLimit.MinValue; | |
| } | |
| private void DisableBuiltInRightClickZoom() | |
| { | |
| if (!m_DisableBuiltInRightClickZoom || m_CameraControllerHandler == null) { | |
| return; | |
| } | |
| if (m_OriginalZoomInputName == null) { | |
| m_OriginalZoomInputName = m_CameraControllerHandler.ZoomInputName; | |
| } | |
| m_CameraControllerHandler.ZoomInputName = "__DisabledByTopDownCameraHack__"; | |
| m_CameraController.TryZoom(false); | |
| } | |
| private void RestoreZoomInputName() | |
| { | |
| if (m_CameraControllerHandler == null || m_OriginalZoomInputName == null) { | |
| return; | |
| } | |
| m_CameraControllerHandler.ZoomInputName = m_OriginalZoomInputName; | |
| } | |
| private void UpdateZoom() | |
| { | |
| var deltaTime = Time.unscaledDeltaTime; | |
| var zoomInput = GetZoomInput(deltaTime); | |
| if (!Mathf.Approximately(zoomInput, 0f)) { | |
| m_TargetViewDistance = Mathf.Clamp(m_TargetViewDistance - zoomInput, m_MinViewDistance, m_MaxViewDistance); | |
| } | |
| m_TopDownView.ViewDistance = Mathf.Lerp(m_TopDownView.ViewDistance, m_TargetViewDistance, 1f - Mathf.Exp(-m_ZoomSmoothSpeed * deltaTime)); | |
| var zoomT = Mathf.InverseLerp(m_MaxViewDistance, m_MinViewDistance, m_TargetViewDistance); | |
| var targetPitchMin = Mathf.Lerp(m_ZoomedOutPitch, m_ZoomedInPitch, zoomT); | |
| m_CurrentPitchMin = Mathf.Lerp(m_CurrentPitchMin, targetPitchMin, 1f - Mathf.Exp(-m_PitchSmoothSpeed * deltaTime)); | |
| var targetPitchMax = Mathf.Clamp(m_CurrentPitchMin + m_PitchRange, m_CurrentPitchMin, m_MaxPitchLimit); | |
| m_TopDownView.PitchLimit = new MinMaxFloat(m_CurrentPitchMin, targetPitchMax); | |
| } | |
| private float GetZoomInput(float deltaTime) | |
| { | |
| var zoomInput = 0f; | |
| #if ENABLE_INPUT_SYSTEM | |
| if (Mouse.current != null) { | |
| var allowMouseWheelZoom = true; | |
| if (m_MouseWheelZoomModifierKey != KeyCode.None) { | |
| allowMouseWheelZoom = Keyboard.current != null && IsModifierKeyPressed(Keyboard.current, m_MouseWheelZoomModifierKey); | |
| } | |
| if (allowMouseWheelZoom) { | |
| zoomInput += Mouse.current.scroll.ReadValue().y * m_MouseWheelZoomStep * 0.05f; | |
| } | |
| } | |
| if (Keyboard.current != null) { | |
| if (Keyboard.current.equalsKey.isPressed || Keyboard.current.numpadPlusKey.isPressed) { | |
| zoomInput += m_KeyboardZoomSpeed * deltaTime; | |
| } | |
| if (Keyboard.current.minusKey.isPressed || Keyboard.current.numpadMinusKey.isPressed) { | |
| zoomInput -= m_KeyboardZoomSpeed * deltaTime; | |
| } | |
| } | |
| if (Gamepad.current != null) { | |
| zoomInput += Gamepad.current.rightStick.ReadValue().y * m_ControllerZoomSpeed * deltaTime; | |
| } | |
| #else | |
| if (m_MouseWheelZoomModifierKey == KeyCode.None || Input.GetKey(m_MouseWheelZoomModifierKey)) { | |
| zoomInput += Input.mouseScrollDelta.y * m_MouseWheelZoomStep; | |
| } | |
| if (Input.GetKey(KeyCode.Equals) || Input.GetKey(KeyCode.KeypadPlus)) { | |
| zoomInput += m_KeyboardZoomSpeed * deltaTime; | |
| } | |
| if (Input.GetKey(KeyCode.Minus) || Input.GetKey(KeyCode.KeypadMinus)) { | |
| zoomInput -= m_KeyboardZoomSpeed * deltaTime; | |
| } | |
| #endif | |
| return zoomInput; | |
| } | |
| private void UpdateMovementMode() | |
| { | |
| if (!m_SwitchToTopDownMovementWhileAiming) { | |
| RestoreDefaultMovementType(); | |
| return; | |
| } | |
| var targetMovementTypeFullName = GetAimMovementTypeFullName(); | |
| var shouldAim = !string.IsNullOrEmpty(targetMovementTypeFullName); | |
| if (!shouldAim) { | |
| m_AimMovementActive = false; | |
| RestoreDefaultMovementType(); | |
| return; | |
| } | |
| if (shouldAim == m_AimMovementActive && m_CharacterLocomotion.MovementTypeFullName == targetMovementTypeFullName) { | |
| return; | |
| } | |
| m_AimMovementActive = shouldAim; | |
| ApplyMovementType(targetMovementTypeFullName); | |
| } | |
| private void RestoreDefaultMovementType() | |
| { | |
| if (m_CharacterLocomotion == null) { | |
| return; | |
| } | |
| if (string.IsNullOrEmpty(m_AdventureMovementTypeFullName)) { | |
| return; | |
| } | |
| m_AimMovementActive = false; | |
| SetThirdPersonMovementType(m_AdventureMovementTypeFullName, true); | |
| } | |
| private void UpdateMouseAimRotation() | |
| { | |
| if (!m_UseSmoothedMouseAimRotation || !IsMouseAimHeld() || m_CameraController == null || m_CharacterLocomotion == null) { | |
| return; | |
| } | |
| var cameraTransform = m_CameraController.CameraTransform; | |
| if (cameraTransform == null) { | |
| return; | |
| } | |
| Vector2 mousePosition; | |
| #if ENABLE_INPUT_SYSTEM | |
| if (Mouse.current == null) { | |
| return; | |
| } | |
| mousePosition = Mouse.current.position.ReadValue(); | |
| #else | |
| mousePosition = Input.mousePosition; | |
| #endif | |
| var ray = cameraTransform.GetComponent<Camera>().ScreenPointToRay(mousePosition); | |
| var characterPosition = m_CharacterLocomotion.transform.position; | |
| var aimPlane = new Plane(m_CharacterLocomotion.Up, characterPosition); | |
| if (!aimPlane.Raycast(ray, out var distance)) { | |
| return; | |
| } | |
| var aimPoint = ray.GetPoint(distance); | |
| var aimDirection = Vector3.ProjectOnPlane(aimPoint - characterPosition, m_CharacterLocomotion.Up); | |
| if (aimDirection.sqrMagnitude < m_MinMouseAimDistance * m_MinMouseAimDistance) { | |
| return; | |
| } | |
| var targetRotation = Quaternion.LookRotation(aimDirection.normalized, m_CharacterLocomotion.Up); | |
| var smoothedRotation = Quaternion.RotateTowards(m_CharacterLocomotion.Rotation, targetRotation, m_MouseAimRotateSpeed * Time.deltaTime); | |
| m_CharacterLocomotion.SetRotation(smoothedRotation, false); | |
| } | |
| private void UpdateControllerRotation() | |
| { | |
| var horizontalAim = GetControllerHorizontalAimInput(); | |
| if (Mathf.Abs(horizontalAim) < m_ControllerAimThreshold) { | |
| return; | |
| } | |
| var currentRotation = m_CharacterLocomotion.Rotation; | |
| var targetRotation = currentRotation * Quaternion.AngleAxis(horizontalAim * m_ControllerRotateSpeed * Time.deltaTime, m_CharacterLocomotion.Up); | |
| m_CharacterLocomotion.SetRotation(targetRotation, false); | |
| } | |
| private bool IsMouseAimHeld() | |
| { | |
| if (m_PlayerInput != null && !string.IsNullOrEmpty(m_AimInputName)) { | |
| return m_PlayerInput.GetButton(m_AimInputName); | |
| } | |
| #if ENABLE_INPUT_SYSTEM | |
| if (Mouse.current != null && Mouse.current.rightButton.isPressed) { | |
| return true; | |
| } | |
| #endif | |
| return Input.GetMouseButton(1); | |
| } | |
| private float GetControllerHorizontalAimInput() | |
| { | |
| #if ENABLE_INPUT_SYSTEM | |
| if (Gamepad.current != null) { | |
| return Gamepad.current.rightStick.ReadValue().x; | |
| } | |
| #endif | |
| return 0f; | |
| } | |
| private string GetAimMovementTypeFullName() | |
| { | |
| if (IsMouseAimHeld()) { | |
| return !string.IsNullOrEmpty(m_TopDownMovementTypeFullName) ? m_TopDownMovementTypeFullName : null; | |
| } | |
| if (m_EnableControllerAimMode && Mathf.Abs(GetControllerHorizontalAimInput()) >= m_ControllerAimThreshold) { | |
| return !string.IsNullOrEmpty(m_TopDownMovementTypeFullName) ? m_TopDownMovementTypeFullName : null; | |
| } | |
| return null; | |
| } | |
| private bool IsAimActiveThisFrame() | |
| { | |
| return !string.IsNullOrEmpty(GetAimMovementTypeFullName()); | |
| } | |
| private void ApplyMovementType(string movementTypeFullName) | |
| { | |
| if (m_CharacterLocomotion == null || string.IsNullOrEmpty(movementTypeFullName)) { | |
| return; | |
| } | |
| ConfigureTopDownMovementType(); | |
| SetThirdPersonMovementType(movementTypeFullName, false); | |
| } | |
| private void SetThirdPersonMovementType(string movementTypeFullName, bool forceActivate) | |
| { | |
| if (m_CharacterLocomotion == null || string.IsNullOrEmpty(movementTypeFullName)) { | |
| return; | |
| } | |
| if (m_CharacterLocomotion.ThirdPersonMovementTypeFullName != movementTypeFullName) { | |
| m_CharacterLocomotion.ThirdPersonMovementTypeFullName = movementTypeFullName; | |
| } | |
| if (forceActivate || m_CharacterLocomotion.MovementTypeFullName != movementTypeFullName || NeedsMovementTypeSwitch(movementTypeFullName)) { | |
| m_CharacterLocomotion.SetMovementType(movementTypeFullName); | |
| } | |
| } | |
| private bool NeedsMovementTypeSwitch(string movementTypeFullName) | |
| { | |
| if (m_CharacterLocomotion == null || string.IsNullOrEmpty(movementTypeFullName)) { | |
| return false; | |
| } | |
| var activeMovementType = m_CharacterLocomotion.ActiveMovementType; | |
| if (activeMovementType == null) { | |
| return true; | |
| } | |
| return activeMovementType.GetType().FullName != movementTypeFullName; | |
| } | |
| private void ConfigureTopDownMovementType() | |
| { | |
| if (m_TopDownMovementType == null) { | |
| return; | |
| } | |
| if (m_ForceTopDownRelativeCameraMovement) { | |
| m_TopDownMovementType.RelativeCameraMovement = true; | |
| } | |
| if (m_ForceTopDownLookAtCursor) { | |
| m_TopDownMovementType.LookInMoveDirection = false; | |
| } | |
| } | |
| private void ConfigureCursorMode() | |
| { | |
| if (!m_KeepMouseVisible || m_CharacterLocomotion == null) { | |
| return; | |
| } | |
| if (!m_CursorStateCached) { | |
| m_PreviousCursorLockMode = Cursor.lockState; | |
| m_PreviousCursorVisible = Cursor.visible; | |
| m_CursorStateCached = true; | |
| } | |
| CacheAndOverrideCursorProperties(m_CharacterLocomotion.GetComponents<MonoBehaviour>()); | |
| CacheAndOverrideCursorProperties(m_CharacterLocomotion.GetComponentsInChildren<MonoBehaviour>(true)); | |
| CacheAndOverrideCursorProperties(m_CharacterLocomotion.GetComponentsInParent<MonoBehaviour>(true)); | |
| Cursor.lockState = CursorLockMode.None; | |
| Cursor.visible = true; | |
| } | |
| private void MaintainMouseVisibility() | |
| { | |
| if (!m_KeepMouseVisible) { | |
| return; | |
| } | |
| if (!Cursor.visible || Cursor.lockState != CursorLockMode.None) { | |
| Cursor.lockState = CursorLockMode.None; | |
| Cursor.visible = true; | |
| } | |
| } | |
| private void RestoreCursorConfiguration() | |
| { | |
| for (int i = 0; i < m_CursorManagedComponents.Count; ++i) { | |
| var component = m_CursorManagedComponents[i]; | |
| if (component == null) { | |
| continue; | |
| } | |
| SetBoolProperty(component, "DisableCursor", m_PreviousDisableCursorValues[i]); | |
| SetBoolProperty(component, "EnableCursorWithEscape", m_PreviousEnableCursorWithEscapeValues[i]); | |
| } | |
| m_CursorManagedComponents.Clear(); | |
| m_PreviousDisableCursorValues.Clear(); | |
| m_PreviousEnableCursorWithEscapeValues.Clear(); | |
| if (!m_CursorStateCached) { | |
| return; | |
| } | |
| Cursor.lockState = m_PreviousCursorLockMode; | |
| Cursor.visible = m_PreviousCursorVisible; | |
| m_CursorStateCached = false; | |
| } | |
| private void CacheAndOverrideCursorProperties(MonoBehaviour[] components) | |
| { | |
| for (int i = 0; i < components.Length; ++i) { | |
| var component = components[i]; | |
| if (component == null) { | |
| continue; | |
| } | |
| if (!TryGetBoolProperty(component, "DisableCursor", out var disableCursorValue)) { | |
| continue; | |
| } | |
| if (m_CursorManagedComponents.Contains(component)) { | |
| continue; | |
| } | |
| var enableCursorWithEscapeValue = false; | |
| TryGetBoolProperty(component, "EnableCursorWithEscape", out enableCursorWithEscapeValue); | |
| m_CursorManagedComponents.Add(component); | |
| m_PreviousDisableCursorValues.Add(disableCursorValue); | |
| m_PreviousEnableCursorWithEscapeValues.Add(enableCursorWithEscapeValue); | |
| SetBoolProperty(component, "DisableCursor", false); | |
| SetBoolProperty(component, "EnableCursorWithEscape", true); | |
| } | |
| } | |
| private IEnumerator EnforceCursorVisibilityAtEndOfFrame() | |
| { | |
| while (true) { | |
| yield return new WaitForEndOfFrame(); | |
| if (!m_KeepMouseVisible) { | |
| continue; | |
| } | |
| if (m_CharacterLocomotion != null) { | |
| ConfigureCursorMode(); | |
| } | |
| if (!Cursor.visible || Cursor.lockState != CursorLockMode.None) { | |
| Cursor.lockState = CursorLockMode.None; | |
| Cursor.visible = true; | |
| } | |
| } | |
| } | |
| private bool TryGetBoolProperty(Component component, string propertyName, out bool value) | |
| { | |
| value = false; | |
| if (component == null) { | |
| return false; | |
| } | |
| var property = component.GetType().GetProperty(propertyName, c_InstanceFlags); | |
| if (property == null || property.PropertyType != typeof(bool) || !property.CanRead) { | |
| return false; | |
| } | |
| value = (bool)property.GetValue(component); | |
| return true; | |
| } | |
| private void SetBoolProperty(Component component, string propertyName, bool value) | |
| { | |
| if (component == null) { | |
| return; | |
| } | |
| var property = component.GetType().GetProperty(propertyName, c_InstanceFlags); | |
| if (property == null || property.PropertyType != typeof(bool) || !property.CanWrite) { | |
| return; | |
| } | |
| property.SetValue(component, value); | |
| } | |
| private void EnsureDebugGuiStyles() | |
| { | |
| if (m_DebugLabelStyle == null) { | |
| m_DebugLabelStyle = new GUIStyle(GUI.skin.label) { | |
| alignment = TextAnchor.UpperLeft, | |
| fontSize = 13, | |
| normal = { textColor = Color.white } | |
| }; | |
| } | |
| if (m_DebugBoxStyle == null) { | |
| var backgroundTexture = new Texture2D(1, 1, TextureFormat.RGBA32, false); | |
| backgroundTexture.SetPixel(0, 0, new Color(0f, 0f, 0f, 0.72f)); | |
| backgroundTexture.Apply(); | |
| m_DebugBoxStyle = new GUIStyle(GUI.skin.box); | |
| m_DebugBoxStyle.normal.background = backgroundTexture; | |
| } | |
| } | |
| private List<string> BuildDebugLines() | |
| { | |
| var lines = new List<string>(8) { | |
| "Controller Debug" | |
| }; | |
| #if ENABLE_INPUT_SYSTEM | |
| var gamepad = Gamepad.current; | |
| if (gamepad == null) { | |
| lines.Add("Connected: no"); | |
| lines.Add("Left Stick: (0.00, 0.00)"); | |
| lines.Add("Right Stick: (0.00, 0.00)"); | |
| lines.Add("L2: 0.00 | pressed: no"); | |
| lines.Add("R2: 0.00 | pressed: no"); | |
| return lines; | |
| } | |
| var leftStick = gamepad.leftStick.ReadValue(); | |
| var rightStick = gamepad.rightStick.ReadValue(); | |
| var leftTrigger = gamepad.leftTrigger.ReadValue(); | |
| var rightTrigger = gamepad.rightTrigger.ReadValue(); | |
| lines.Add("Connected: yes"); | |
| lines.Add($"Name: {gamepad.displayName}"); | |
| lines.Add($"Left Stick: ({leftStick.x:F2}, {leftStick.y:F2}) | mag {leftStick.magnitude:F2}"); | |
| lines.Add($"Right Stick: ({rightStick.x:F2}, {rightStick.y:F2}) | mag {rightStick.magnitude:F2}"); | |
| lines.Add($"L2: {leftTrigger:F2} | pressed: {BoolToYesNo(leftTrigger >= m_DebugTriggerPressedThreshold)}"); | |
| lines.Add($"R2: {rightTrigger:F2} | pressed: {BoolToYesNo(rightTrigger >= m_DebugTriggerPressedThreshold)}"); | |
| #else | |
| var joystickNames = Input.GetJoystickNames(); | |
| var connected = joystickNames != null && joystickNames.Length > 0 && !string.IsNullOrEmpty(joystickNames[0]); | |
| var leftStick = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")); | |
| var rightStick = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y")); | |
| lines.Add($"Connected: {BoolToYesNo(connected)}"); | |
| lines.Add($"Name: {(connected ? joystickNames[0] : "n/a")}"); | |
| lines.Add($"Left Stick: ({leftStick.x:F2}, {leftStick.y:F2}) | mag {leftStick.magnitude:F2}"); | |
| lines.Add($"Right Stick: ({rightStick.x:F2}, {rightStick.y:F2}) | mag {rightStick.magnitude:F2}"); | |
| lines.Add("L2: n/a"); | |
| lines.Add("R2: n/a"); | |
| #endif | |
| return lines; | |
| } | |
| private string BoolToYesNo(bool value) | |
| { | |
| return value ? "yes" : "no"; | |
| } | |
| #if ENABLE_INPUT_SYSTEM | |
| private bool IsModifierKeyPressed(Keyboard keyboard, KeyCode keyCode) | |
| { | |
| if (keyboard == null) { | |
| return false; | |
| } | |
| switch (keyCode) { | |
| case KeyCode.V: | |
| return keyboard.vKey.isPressed; | |
| case KeyCode.LeftShift: | |
| return keyboard.leftShiftKey.isPressed; | |
| case KeyCode.RightShift: | |
| return keyboard.rightShiftKey.isPressed; | |
| case KeyCode.LeftControl: | |
| return keyboard.leftCtrlKey.isPressed; | |
| case KeyCode.RightControl: | |
| return keyboard.rightCtrlKey.isPressed; | |
| case KeyCode.LeftAlt: | |
| return keyboard.leftAltKey.isPressed; | |
| case KeyCode.RightAlt: | |
| return keyboard.rightAltKey.isPressed; | |
| case KeyCode.Space: | |
| return keyboard.spaceKey.isPressed; | |
| default: | |
| return false; | |
| } | |
| } | |
| #endif | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment