update
This commit is contained in:
@@ -1,42 +1,194 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using Opsive.UltimateCharacterController.Input.VirtualControls;
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Input
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Input.VirtualControls;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Acts as a common base class for input using the Unity Input Manager. Works with keyboard/mouse, controller, and mobile input.
|
||||
/// </summary>
|
||||
public class UnityInput : PlayerInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies if any input type should be forced.
|
||||
/// </summary>
|
||||
public enum ForceInputType { None, Standalone, Virtual }
|
||||
[Header("New Input System Settings")]
|
||||
[Tooltip("The Input Action Asset containing your mappings.")]
|
||||
[SerializeField] private InputActionAsset m_InputAsset;
|
||||
[Tooltip("The name of the Action Map to use (e.g., 'Player' or 'Gameplay').")]
|
||||
[SerializeField] private string m_ActionMapName = "Gameplay";
|
||||
|
||||
[Tooltip("Specifies if any input type should be forced.")]
|
||||
[SerializeField] protected ForceInputType m_ForceInput;
|
||||
[Tooltip("Should the cursor be disabled?")]
|
||||
[Header("Standalone Settings")]
|
||||
[SerializeField] protected bool m_ForceInput;
|
||||
[SerializeField] protected bool m_DisableCursor = true;
|
||||
[Tooltip("Should the cursor be enabled when the escape key is pressed?")]
|
||||
[SerializeField] protected bool m_EnableCursorWithEscape = true;
|
||||
[Tooltip("If the cursor is enabled with escape should the look vector be prevented from updating?")]
|
||||
[SerializeField] protected bool m_PreventLookVectorChanges = true;
|
||||
[Tooltip("The joystick is considered up when the raw value is less than the specified threshold.")]
|
||||
[Range(0, 1)] [SerializeField] protected float m_JoystickUpThreshold = 1;
|
||||
|
||||
public bool DisableCursor { get { return m_DisableCursor; }
|
||||
set
|
||||
{
|
||||
if (m_Input is VirtualInput) {
|
||||
m_DisableCursor = false;
|
||||
[Header("Controller")]
|
||||
[Range(0, 1)]
|
||||
[SerializeField] protected float m_JoystickUpThreshold = 1f;
|
||||
|
||||
private InputActionMap m_ActionMap;
|
||||
private readonly Dictionary<string, InputAction> m_ActionCache = new Dictionary<string, InputAction>();
|
||||
private VirtualControlsManager m_VirtualControlsManager;
|
||||
|
||||
protected override bool CanCheckForController => false;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (m_InputAsset == null) {
|
||||
Debug.LogError("Error: No Input Action Asset assigned to UnityInput.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
m_ActionMap = m_InputAsset.FindActionMap(m_ActionMapName);
|
||||
if (m_ActionMap == null) {
|
||||
Debug.LogError($"Error: Action Map '{m_ActionMapName}' not found in {m_InputAsset.name}. Check the name in the Asset editor.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache all actions for high-performance string lookup
|
||||
m_ActionCache.Clear();
|
||||
foreach (var action in m_ActionMap.actions) {
|
||||
m_ActionCache[action.name] = action;
|
||||
}
|
||||
|
||||
m_ActionMap.Enable();
|
||||
Debug.Log($"UnityInput: Initialized with Action Map '{m_ActionMapName}' and cached {m_ActionCache.Count} actions.");
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
m_ActionMap?.Enable();
|
||||
|
||||
if (m_DisableCursor) {
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
Cursor.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
m_ActionMap?.Disable();
|
||||
}
|
||||
|
||||
// --- OPSIVE OVERRIDES ---
|
||||
|
||||
protected override bool GetButtonInternal(string name)
|
||||
{
|
||||
// 1. Virtual Controls (Mobile UI)
|
||||
if (m_VirtualControlsManager != null && m_VirtualControlsManager.GetButton(name, InputBase.ButtonAction.GetButton)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Hardware Input
|
||||
if (m_ActionCache.TryGetValue(name, out var action)) {
|
||||
return action.IsPressed();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool GetButtonDownInternal(string name)
|
||||
{
|
||||
if (m_VirtualControlsManager != null && m_VirtualControlsManager.GetButton(name, InputBase.ButtonAction.GetButtonDown)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_ActionCache.TryGetValue(name, out var action)) {
|
||||
return action.WasPressedThisFrame();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool GetButtonUpInternal(string name)
|
||||
{
|
||||
if (m_VirtualControlsManager != null && m_VirtualControlsManager.GetButton(name, InputBase.ButtonAction.GetButtonUp)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_ActionCache.TryGetValue(name, out var action)) {
|
||||
return action.WasReleasedThisFrame();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override float GetAxisInternal(string name)
|
||||
{
|
||||
if (m_VirtualControlsManager != null) {
|
||||
var vValue = m_VirtualControlsManager.GetAxis(name);
|
||||
if (vValue != 0) return vValue;
|
||||
}
|
||||
|
||||
// --- SPECIAL CASE: ALIASING ---
|
||||
// If Opsive asks for standard names but your Asset uses Vector2 Move/Look
|
||||
|
||||
// 1. Movement Aliasing
|
||||
if (name == "Horizontal" || name == "Vertical") {
|
||||
if (m_ActionCache.TryGetValue("Move", out var moveAction)) {
|
||||
var moveVal = moveAction.ReadValue<Vector2>();
|
||||
return name == "Horizontal" ? moveVal.x : moveVal.y;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Look/Mouse Aliasing
|
||||
if (name == "Mouse X" || name == "Mouse Y") {
|
||||
if (m_ActionCache.TryGetValue("Look", out var lookAction)) {
|
||||
var lookVal = lookAction.ReadValue<Vector2>();
|
||||
return name == "Mouse X" ? lookVal.x : lookVal.y;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Direct Float lookup
|
||||
if (m_ActionCache.TryGetValue(name, out var action)) {
|
||||
return action.ReadValue<float>();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override float GetAxisRawInternal(string name) => GetAxisInternal(name);
|
||||
|
||||
public override Vector2 GetMousePosition() => Mouse.current != null ? Mouse.current.position.ReadValue() : Vector2.zero;
|
||||
|
||||
// --- VIRTUAL CONTROLS INTEGRATION ---
|
||||
|
||||
public bool RegisterVirtualControlsManager(VirtualControlsManager virtualControlsManager)
|
||||
{
|
||||
m_VirtualControlsManager = virtualControlsManager;
|
||||
return m_VirtualControlsManager != null;
|
||||
}
|
||||
|
||||
public void UnegisterVirtualControlsManager()
|
||||
{
|
||||
m_VirtualControlsManager = null;
|
||||
}
|
||||
|
||||
// --- UTILITY ---
|
||||
|
||||
protected override void OnApplicationFocus(bool hasFocus)
|
||||
{
|
||||
if (m_ForceInput) {
|
||||
hasFocus = true;
|
||||
}
|
||||
|
||||
base.OnApplicationFocus(hasFocus);
|
||||
|
||||
if (enabled && hasFocus && m_DisableCursor) {
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
Cursor.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ForceInput
|
||||
{
|
||||
get => m_ForceInput;
|
||||
set => m_ForceInput = value;
|
||||
}
|
||||
|
||||
public bool DisableCursor
|
||||
{
|
||||
get => m_DisableCursor;
|
||||
set {
|
||||
m_DisableCursor = value;
|
||||
if (m_DisableCursor && Cursor.visible) {
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
@@ -47,259 +199,23 @@ namespace Opsive.UltimateCharacterController.Input
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool EnableCursorWithEscape { get { return m_EnableCursorWithEscape; } set { m_EnableCursorWithEscape = value; } }
|
||||
public bool PreventLookMovementWithEscape { get { return m_PreventLookVectorChanges; } set { m_PreventLookVectorChanges = value; } }
|
||||
public float JoystickUpThreshold { get { return m_JoystickUpThreshold; } set { m_JoystickUpThreshold = value; } }
|
||||
|
||||
private InputBase m_Input;
|
||||
private bool m_UseVirtualInput;
|
||||
private HashSet<string> m_JoystickDownSet;
|
||||
private HashSet<string> m_ToAddJoystickDownSet;
|
||||
private HashSet<string> m_ToRemoveJoystickDownSet;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
public bool EnableCursorWithEscape
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_UseVirtualInput = m_ForceInput == ForceInputType.Virtual;
|
||||
#if !UNITY_EDITOR && (UNITY_IPHONE || UNITY_ANDROID || UNITY_WP_8_1 || UNITY_BLACKBERRY)
|
||||
if (m_ForceInput != ForceInputType.Standalone) {
|
||||
m_UseVirtualInput = true;
|
||||
}
|
||||
#endif
|
||||
if (m_UseVirtualInput) {
|
||||
m_Input = new VirtualInput();
|
||||
// The cursor must be enabled for virtual controls to allow the drag events to occur.
|
||||
m_DisableCursor = false;
|
||||
} else {
|
||||
m_Input = new StandaloneInput();
|
||||
}
|
||||
m_Input.Initialize(this);
|
||||
get => m_EnableCursorWithEscape;
|
||||
set => m_EnableCursorWithEscape = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The component has been enabled.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
public bool PreventLookMovementWithEscape
|
||||
{
|
||||
if (!m_UseVirtualInput && m_DisableCursor) {
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
Cursor.visible = false;
|
||||
}
|
||||
get => m_PreventLookVectorChanges;
|
||||
set => m_PreventLookVectorChanges = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Associates the VirtualControlsManager with the VirtualInput object.
|
||||
/// </summary>
|
||||
/// <param name="virtualControlsManager">The VirtualControlsManager to associate with the VirtualInput object.</param>
|
||||
/// <returns>True if the virtual controls were registered.</returns>
|
||||
public bool RegisterVirtualControlsManager(VirtualControlsManager virtualControlsManager)
|
||||
public float JoystickUpThreshold
|
||||
{
|
||||
if (m_Input is VirtualInput) {
|
||||
(m_Input as VirtualInput).RegisterVirtualControlsManager(virtualControlsManager);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the VirtualControlsManager association.
|
||||
/// </summary>
|
||||
public void UnegisterVirtualControlsManager()
|
||||
{
|
||||
if (m_Input is VirtualInput) {
|
||||
(m_Input as VirtualInput).UnregisterVirtualControlsManager();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the joystick and cursor state values.
|
||||
/// </summary>
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (m_UseVirtualInput) {
|
||||
return;
|
||||
}
|
||||
// The joystick is no longer down after the axis is 0.
|
||||
if (IsControllerConnected()) {
|
||||
// GetButtonUp/Down doesn't immediately add the button name to the set to prevent the GetButtonUp/Down from returning false
|
||||
// if it is called twice within the same frame. Add it after the frame has ended.
|
||||
if (m_JoystickDownSet != null) {
|
||||
foreach (var item in m_JoystickDownSet) {
|
||||
if (Mathf.Abs(m_Input.GetAxisRaw(item)) < m_JoystickUpThreshold) {
|
||||
m_ToRemoveJoystickDownSet.Add(item);
|
||||
}
|
||||
}
|
||||
foreach (var item in m_ToRemoveJoystickDownSet) {
|
||||
m_JoystickDownSet.Remove(item);
|
||||
}
|
||||
m_ToRemoveJoystickDownSet.Clear();
|
||||
}
|
||||
if (m_ToAddJoystickDownSet != null && m_ToAddJoystickDownSet.Count > 0) {
|
||||
if (m_JoystickDownSet == null) {
|
||||
m_JoystickDownSet = new HashSet<string>();
|
||||
m_ToRemoveJoystickDownSet = new HashSet<string>();
|
||||
}
|
||||
foreach (var item in m_ToAddJoystickDownSet) {
|
||||
m_JoystickDownSet.Add(item);
|
||||
}
|
||||
m_ToAddJoystickDownSet.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the cursor if the escape key is pressed. Disable the cursor if it is visbile but should be disabled upon press.
|
||||
if (m_EnableCursorWithEscape && UnityEngine.Input.GetKeyDown(KeyCode.Escape)) {
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
if (m_PreventLookVectorChanges) {
|
||||
OnApplicationFocus(false);
|
||||
}
|
||||
} else if (Cursor.visible && m_DisableCursor && !IsPointerOverUI() && (UnityEngine.Input.GetKeyDown(KeyCode.Mouse0) || UnityEngine.Input.GetKeyDown(KeyCode.Mouse1))) {
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
Cursor.visible = false;
|
||||
if (m_PreventLookVectorChanges) {
|
||||
OnApplicationFocus(true);
|
||||
}
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
// The cursor should be visible when the game is paused.
|
||||
if (!Cursor.visible && Time.deltaTime == 0 && !m_DisableCursor) {
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method which returns true if the button is being pressed.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the button.</param>
|
||||
/// <returns>True of the button is being pressed.</returns>
|
||||
protected override bool GetButtonInternal(string name)
|
||||
{
|
||||
if (m_Input.GetButton(name, InputBase.ButtonAction.GetButton)) {
|
||||
return true;
|
||||
}
|
||||
if (IsControllerConnected()) {
|
||||
if (Mathf.Abs(m_Input.GetAxisRaw(name)) == 1) {
|
||||
if (m_JoystickDownSet == null || !m_JoystickDownSet.Contains(name)) {
|
||||
if (m_ToAddJoystickDownSet == null) {
|
||||
m_ToAddJoystickDownSet = new HashSet<string>();
|
||||
}
|
||||
m_ToAddJoystickDownSet.Add(name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method which returns true if the button was pressed this frame.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the button.</param>
|
||||
/// <returns>True if the button is pressed this frame.</returns>
|
||||
protected override bool GetButtonDownInternal(string name)
|
||||
{
|
||||
if (IsControllerConnected() && Mathf.Abs(m_Input.GetAxisRaw(name)) == 1) {
|
||||
// The button should only be considered down on the first frame.
|
||||
if (m_JoystickDownSet != null && m_JoystickDownSet.Contains(name)) {
|
||||
return false;
|
||||
}
|
||||
if (m_ToAddJoystickDownSet == null) {
|
||||
m_ToAddJoystickDownSet = new HashSet<string>();
|
||||
}
|
||||
m_ToAddJoystickDownSet.Add(name);
|
||||
return true;
|
||||
}
|
||||
return m_Input.GetButton(name, InputBase.ButtonAction.GetButtonDown);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method which returnstrue if the button is up.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the button.</param>
|
||||
/// <returns>True if the button is up.</returns>
|
||||
protected override bool GetButtonUpInternal(string name)
|
||||
{
|
||||
if (IsControllerConnected()) {
|
||||
if (Mathf.Abs(m_Input.GetAxisRaw(name)) == 1 && (m_JoystickDownSet == null || !m_JoystickDownSet.Contains(name))) {
|
||||
if (m_ToAddJoystickDownSet == null) {
|
||||
m_ToAddJoystickDownSet = new HashSet<string>();
|
||||
}
|
||||
m_ToAddJoystickDownSet.Add(name);
|
||||
return false;
|
||||
}
|
||||
if (m_JoystickDownSet != null && m_JoystickDownSet.Contains(name) && m_Input.GetAxisRaw(name) < m_JoystickUpThreshold) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return m_Input.GetButton(name, InputBase.ButtonAction.GetButtonUp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method which returns the value of the axis with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the axis.</param>
|
||||
/// <returns>The value of the axis.</returns>
|
||||
protected override float GetAxisInternal(string name)
|
||||
{
|
||||
return m_Input.GetAxis(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method which returns the value of the raw axis with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the axis.</param>
|
||||
/// <returns>The value of the raw axis.</returns>
|
||||
protected override float GetAxisRawInternal(string name)
|
||||
{
|
||||
return m_Input.GetAxisRaw(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the pointer is over a UI element.
|
||||
/// </summary>
|
||||
/// <returns>True if the pointer is over a UI element.</returns>
|
||||
public override bool IsPointerOverUI()
|
||||
{
|
||||
// The input will always be over a UI element with virtual inputs.
|
||||
if (m_Input is VirtualInput) {
|
||||
return false;
|
||||
}
|
||||
return base.IsPointerOverUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables gameplay input. An example of when it will not be enabled is when there is a fullscreen UI over the main camera.
|
||||
/// </summary>
|
||||
/// <param name="enable">True if the input is enabled.</param>
|
||||
protected override void EnableGameplayInput(bool enable)
|
||||
{
|
||||
base.EnableGameplayInput(enable);
|
||||
|
||||
if (enable && m_DisableCursor) {
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
Cursor.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the game have focus?
|
||||
/// </summary>
|
||||
/// <param name="hasFocus">True if the game has focus.</param>
|
||||
protected override void OnApplicationFocus(bool hasFocus)
|
||||
{
|
||||
base.OnApplicationFocus(hasFocus);
|
||||
|
||||
if (enabled && hasFocus && m_DisableCursor) {
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
Cursor.visible = false;
|
||||
}
|
||||
get => m_JoystickUpThreshold;
|
||||
set => m_JoystickUpThreshold = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user