清理项目目录,引入 InputModule 做多平台输入适配
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.InputModule.Runtime
|
||||
{
|
||||
public static class BindingOverridePersistence
|
||||
{
|
||||
public const int CurrentVersion = 2;
|
||||
|
||||
[Serializable]
|
||||
private class Container
|
||||
{
|
||||
public int version;
|
||||
public string payload;
|
||||
}
|
||||
|
||||
public static string Serialize(string overridesJson)
|
||||
{
|
||||
Container container = new Container { version = CurrentVersion, payload = overridesJson };
|
||||
return JsonUtility.ToJson(container);
|
||||
}
|
||||
|
||||
public static bool TryDeserialize(string storedData, out string overridesJson)
|
||||
{
|
||||
overridesJson = null;
|
||||
if (string.IsNullOrWhiteSpace(storedData))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string trimmed = storedData.TrimStart();
|
||||
|
||||
if (trimmed.StartsWith("["))
|
||||
{
|
||||
overridesJson = storedData;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (trimmed.StartsWith("{"))
|
||||
{
|
||||
Container container;
|
||||
try
|
||||
{
|
||||
container = JsonUtility.FromJson<Container>(storedData);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
Log.Warning("[InputModule] Binding overrides data is corrupted. Skipping load.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (container == null)
|
||||
{
|
||||
Log.Warning("[InputModule] Binding overrides data is corrupted. Skipping load.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (container.version > CurrentVersion)
|
||||
{
|
||||
Log.Warning("[InputModule] Binding overrides data version {0} is newer than supported version {1}. Skipping load.",
|
||||
container.version, CurrentVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
overridesJson = container.payload;
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.Warning("[InputModule] Unrecognized binding overrides data format. Skipping load.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a41e1727c0974741972bf59521aae50
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace SepCore.InputModule.Runtime
|
||||
{
|
||||
internal static class InputDeviceKindUtility
|
||||
{
|
||||
public static InputDeviceKind FromDevice(InputDevice device)
|
||||
{
|
||||
if (device == null)
|
||||
{
|
||||
return InputDeviceKind.Unknown;
|
||||
}
|
||||
|
||||
if (device is Gamepad)
|
||||
{
|
||||
return InputDeviceKind.Gamepad;
|
||||
}
|
||||
|
||||
if (device is Touchscreen)
|
||||
{
|
||||
return InputDeviceKind.Touch;
|
||||
}
|
||||
|
||||
if (device is Keyboard || device is Mouse)
|
||||
{
|
||||
return InputDeviceKind.KeyboardMouse;
|
||||
}
|
||||
|
||||
return InputDeviceKind.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfceff161ff25aa4fa504b3334f23233
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,839 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.InputModule.Runtime
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Input Module")]
|
||||
public sealed class InputModuleComponent : GameFrameworkComponent
|
||||
{
|
||||
private const string DefaultBindingOverrideSettingKey = "SepCore.InputModule.BindingOverrides";
|
||||
|
||||
[SerializeField] private InputActionAsset _inputActionsAsset = null;
|
||||
[SerializeField] private InputContextId _startupContext = InputContextId.None;
|
||||
[SerializeField] private bool _loadBindingOverridesOnInit = true;
|
||||
[SerializeField] private bool _saveBindingOverridesOnDestroy = false;
|
||||
[SerializeField] private string _bindingOverrideSettingKey = DefaultBindingOverrideSettingKey;
|
||||
|
||||
private readonly Dictionary<InputActionId, Action<InputCommand>> _listeners =
|
||||
new Dictionary<InputActionId, Action<InputCommand>>();
|
||||
|
||||
private readonly Dictionary<InputContextId, InputActionMap> _actionMaps =
|
||||
new Dictionary<InputContextId, InputActionMap>();
|
||||
|
||||
private readonly InputContextStack _contextStack = new InputContextStack();
|
||||
|
||||
private InputActionAsset _runtimeActions = null;
|
||||
private SettingComponent _settingComponent = null;
|
||||
private bool _isInitialized = false;
|
||||
private bool _onInitCalled = false;
|
||||
private bool _ownsRuntimeActions = false;
|
||||
private InputActionRebindingExtensions.RebindingOperation _activeRebindOperation;
|
||||
private bool _rebindCanceledByUser;
|
||||
private IInputPromptMap _promptMap;
|
||||
|
||||
public event Action<InputCommand> CommandTriggered;
|
||||
|
||||
public event Action<InputDeviceKind> DeviceKindChanged;
|
||||
|
||||
public event Action<InputContextId> ContextChanged;
|
||||
|
||||
public event Action<RebindResult> RebindCompleted;
|
||||
|
||||
public InputDeviceKind CurrentDeviceKind { get; private set; } = InputDeviceKind.Unknown;
|
||||
|
||||
public InputContextId CurrentContext => _contextStack.Current;
|
||||
|
||||
public bool IsInitialized => _isInitialized;
|
||||
|
||||
public bool IsRebinding => _activeRebindOperation != null;
|
||||
|
||||
public IInputPromptMap PromptMap
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_promptMap == null)
|
||||
{
|
||||
_promptMap = new InputModuleDefaultPromptMap(_runtimeActions);
|
||||
}
|
||||
|
||||
return _promptMap;
|
||||
}
|
||||
|
||||
set => _promptMap = value;
|
||||
}
|
||||
|
||||
public InputActionAsset RuntimeActions
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureInitialized();
|
||||
return _runtimeActions;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
ApplyContextState();
|
||||
SubscribeToGlobalDeviceTracking();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
UnsubscribeFromGlobalDeviceTracking();
|
||||
_runtimeActions?.Disable();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CancelRebinding();
|
||||
|
||||
if (_saveBindingOverridesOnDestroy)
|
||||
{
|
||||
SaveBindingOverrides();
|
||||
}
|
||||
|
||||
UnsubscribeFromGlobalDeviceTracking();
|
||||
UnsubscribeFromActions();
|
||||
|
||||
if (_ownsRuntimeActions && _runtimeActions != null)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Destroy(_runtimeActions);
|
||||
}
|
||||
else
|
||||
{
|
||||
DestroyImmediate(_runtimeActions);
|
||||
}
|
||||
}
|
||||
|
||||
_runtimeActions = null;
|
||||
_actionMaps.Clear();
|
||||
_listeners.Clear();
|
||||
_isInitialized = false;
|
||||
_onInitCalled = false;
|
||||
}
|
||||
|
||||
public void RegisterListener(InputActionId actionId, Action<InputCommand> listener)
|
||||
{
|
||||
if (listener == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureInitialized();
|
||||
_listeners[actionId] = _listeners.TryGetValue(actionId, out Action<InputCommand> existing)
|
||||
? existing + listener
|
||||
: listener;
|
||||
}
|
||||
|
||||
public bool TryGetPrompt(InputActionId actionId, out InputPrompt prompt)
|
||||
{
|
||||
return PromptMap.TryGetPrompt(actionId, CurrentDeviceKind, out prompt);
|
||||
}
|
||||
|
||||
public void InjectCommand(InputCommand command)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CommandTriggered?.Invoke(command);
|
||||
|
||||
if (_listeners.TryGetValue(command.ActionId, out Action<InputCommand> listener))
|
||||
{
|
||||
listener?.Invoke(command);
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceDeviceKind(InputDeviceKind deviceKind)
|
||||
{
|
||||
if (deviceKind == InputDeviceKind.Unknown || deviceKind == CurrentDeviceKind)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InputDeviceKind previous = CurrentDeviceKind;
|
||||
CurrentDeviceKind = deviceKind;
|
||||
Log.Debug("[InputModule] Device changed (forced): {0} -> {1}", previous, deviceKind);
|
||||
DeviceKindChanged?.Invoke(CurrentDeviceKind);
|
||||
}
|
||||
|
||||
public void UnregisterListener(InputActionId actionId, Action<InputCommand> listener)
|
||||
{
|
||||
if (listener == null || !_listeners.TryGetValue(actionId, out Action<InputCommand> existing))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
existing -= listener;
|
||||
if (existing == null)
|
||||
{
|
||||
_listeners.Remove(actionId);
|
||||
return;
|
||||
}
|
||||
|
||||
_listeners[actionId] = existing;
|
||||
}
|
||||
|
||||
public void OnInit()
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Log.Debug("[InputModule] OnInit skipped - already initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
_onInitCalled = true;
|
||||
EnsureInitialized();
|
||||
ApplyContextState();
|
||||
}
|
||||
|
||||
public void SetContext(InputContextId context)
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (_contextStack.Set(context))
|
||||
{
|
||||
ApplyContextState();
|
||||
NotifyContextChanged();
|
||||
Log.Debug("[InputModule] SetContext -> {0}", context);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUIContext()
|
||||
{
|
||||
SetContext(InputContextId.UI);
|
||||
}
|
||||
|
||||
public void SetGameplayExploreContext()
|
||||
{
|
||||
SetContext(InputContextId.GameplayExplore);
|
||||
}
|
||||
|
||||
public void PushContext(InputContextId context)
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (_contextStack.Push(context))
|
||||
{
|
||||
ApplyContextState();
|
||||
NotifyContextChanged();
|
||||
Log.Debug("[InputModule] PushContext -> {0} (stack depth: {1})", context, _contextStack.Count);
|
||||
}
|
||||
}
|
||||
|
||||
public bool PopContext()
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (!_contextStack.Pop(out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ApplyContextState();
|
||||
NotifyContextChanged();
|
||||
Log.Debug("[InputModule] PopContext -> {0} (stack depth: {1})", _contextStack.Current, _contextStack.Count);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClearContexts()
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (_contextStack.Clear())
|
||||
{
|
||||
ApplyContextState();
|
||||
NotifyContextChanged();
|
||||
Log.Debug("[InputModule] ClearContexts");
|
||||
}
|
||||
}
|
||||
|
||||
public bool StartRebinding(
|
||||
InputContextId contextId,
|
||||
InputActionId actionId,
|
||||
int bindingIndex,
|
||||
float timeoutSeconds = 5f,
|
||||
InputDeviceKind expectedDeviceKind = InputDeviceKind.Unknown)
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
if (_activeRebindOperation != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetAction(contextId, actionId, out InputAction action))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bindingIndex < 0 || bindingIndex >= action.bindings.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
InputBinding binding = action.bindings[bindingIndex];
|
||||
string previousEffectivePath = binding.effectivePath;
|
||||
string previousOverridePath = binding.overridePath;
|
||||
_rebindCanceledByUser = false;
|
||||
|
||||
PushContext(InputContextId.Rebind);
|
||||
|
||||
try
|
||||
{
|
||||
DisableActionForRebind(action);
|
||||
_activeRebindOperation = CreateRebindOperation(
|
||||
action,
|
||||
contextId,
|
||||
actionId,
|
||||
bindingIndex,
|
||||
timeoutSeconds,
|
||||
expectedDeviceKind,
|
||||
previousEffectivePath,
|
||||
previousOverridePath);
|
||||
|
||||
_activeRebindOperation.Start();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_activeRebindOperation?.Dispose();
|
||||
_activeRebindOperation = null;
|
||||
|
||||
if (_contextStack.Current == InputContextId.Rebind)
|
||||
{
|
||||
PopContext();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
RebindCompleted?.Invoke(new RebindResult(RebindStatus.Started, contextId, actionId, bindingIndex, previousEffectivePath, previousEffectivePath));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CancelRebinding()
|
||||
{
|
||||
if (_activeRebindOperation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rebindCanceledByUser = true;
|
||||
_activeRebindOperation.Cancel();
|
||||
}
|
||||
|
||||
public void ResetBindingToDefault(InputContextId contextId, InputActionId actionId, int bindingIndex, bool saveAfterReset = false)
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (!TryGetAction(contextId, actionId, out InputAction action))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (bindingIndex < 0 || bindingIndex >= action.bindings.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.RemoveBindingOverride(bindingIndex);
|
||||
|
||||
if (saveAfterReset)
|
||||
{
|
||||
SaveBindingOverrides();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetActionToDefaults(InputContextId contextId, InputActionId actionId, bool saveAfterReset = false)
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (!TryGetAction(contextId, actionId, out InputAction action))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.RemoveAllBindingOverrides();
|
||||
|
||||
if (saveAfterReset)
|
||||
{
|
||||
SaveBindingOverrides();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetAllBindingsToDefaults(bool saveAfterReset = false)
|
||||
{
|
||||
EnsureInitialized();
|
||||
_runtimeActions.RemoveAllBindingOverrides();
|
||||
|
||||
if (saveAfterReset)
|
||||
{
|
||||
SaveBindingOverrides();
|
||||
}
|
||||
}
|
||||
|
||||
public InputBindingSnapshot SaveBindingOverrides()
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
string overridesJson = _runtimeActions.SaveBindingOverridesAsJson();
|
||||
string storedData = BindingOverridePersistence.Serialize(overridesJson);
|
||||
SettingComponent settingComponent = GetSettingComponent();
|
||||
if (settingComponent != null)
|
||||
{
|
||||
settingComponent.SetString(_bindingOverrideSettingKey, storedData);
|
||||
}
|
||||
|
||||
return new InputBindingSnapshot(overridesJson);
|
||||
}
|
||||
|
||||
public void LoadBindingOverrides()
|
||||
{
|
||||
EnsureInitialized();
|
||||
LoadBindingOverridesCore();
|
||||
}
|
||||
|
||||
private void LoadBindingOverridesCore()
|
||||
{
|
||||
if (_runtimeActions == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SettingComponent settingComponent = GetSettingComponent();
|
||||
if (settingComponent == null || !settingComponent.HasSetting(_bindingOverrideSettingKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string storedData = settingComponent.GetString(_bindingOverrideSettingKey);
|
||||
if (!BindingOverridePersistence.TryDeserialize(storedData, out string overridesJson))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(overridesJson))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_runtimeActions.LoadBindingOverridesFromJson(overridesJson);
|
||||
}
|
||||
|
||||
public void ApplyBindingSnapshot(InputBindingSnapshot snapshot, bool saveAfterApply = false)
|
||||
{
|
||||
EnsureInitialized();
|
||||
_runtimeActions.LoadBindingOverridesFromJson(snapshot.OverridesJson);
|
||||
|
||||
if (saveAfterApply)
|
||||
{
|
||||
SaveBindingOverrides();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_runtimeActions = _inputActionsAsset != null
|
||||
? Instantiate(_inputActionsAsset)
|
||||
: InputModuleDefaultActions.Create();
|
||||
_ownsRuntimeActions = true;
|
||||
|
||||
CacheActionMaps();
|
||||
SubscribeToActions();
|
||||
_isInitialized = true;
|
||||
SubscribeToGlobalDeviceTracking();
|
||||
|
||||
if (_promptMap is InputModuleDefaultPromptMap)
|
||||
{
|
||||
_promptMap = null;
|
||||
}
|
||||
|
||||
int mapCount = _actionMaps.Count;
|
||||
if (mapCount == 0)
|
||||
{
|
||||
Log.Warning("[InputModule] Initialized but no action maps were cached. Check that action map names match InputContextId enum values.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info("[InputModule] Initialized. Source: {0}, Cached maps: {1}, Startup context: {2}",
|
||||
_inputActionsAsset != null ? "Serialized asset" : "Default actions",
|
||||
mapCount,
|
||||
_startupContext);
|
||||
}
|
||||
|
||||
if (_loadBindingOverridesOnInit)
|
||||
{
|
||||
LoadBindingOverridesCore();
|
||||
}
|
||||
|
||||
_contextStack.Set(_startupContext);
|
||||
|
||||
if (!_onInitCalled)
|
||||
{
|
||||
Log.Warning("[InputModule] Auto-initialized before OnInit() was called. Action maps will not be enabled until OnInit() is invoked.");
|
||||
}
|
||||
}
|
||||
|
||||
private void CacheActionMaps()
|
||||
{
|
||||
_actionMaps.Clear();
|
||||
foreach (InputActionMap map in _runtimeActions.actionMaps)
|
||||
{
|
||||
if (TryParseContextId(map.name, out InputContextId contextId))
|
||||
{
|
||||
_actionMaps[contextId] = map;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning("[InputModule] ActionMap '{0}' does not match any InputContextId and will be ignored.", map.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeToActions()
|
||||
{
|
||||
foreach (InputAction action in _runtimeActions)
|
||||
{
|
||||
action.started += OnActionTriggered;
|
||||
action.performed += OnActionTriggered;
|
||||
action.canceled += OnActionTriggered;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnsubscribeFromActions()
|
||||
{
|
||||
if (_runtimeActions == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (InputAction action in _runtimeActions)
|
||||
{
|
||||
action.started -= OnActionTriggered;
|
||||
action.performed -= OnActionTriggered;
|
||||
action.canceled -= OnActionTriggered;
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeToGlobalDeviceTracking()
|
||||
{
|
||||
InputSystem.onActionChange -= OnGlobalActionChange;
|
||||
InputSystem.onActionChange += OnGlobalActionChange;
|
||||
}
|
||||
|
||||
private void UnsubscribeFromGlobalDeviceTracking()
|
||||
{
|
||||
InputSystem.onActionChange -= OnGlobalActionChange;
|
||||
}
|
||||
|
||||
private void OnGlobalActionChange(object obj, InputActionChange change)
|
||||
{
|
||||
if (change == InputActionChange.ActionPerformed && obj is InputAction.CallbackContext context)
|
||||
{
|
||||
UpdateDeviceKind(context);
|
||||
}
|
||||
}
|
||||
|
||||
// Disables all action maps, then enables Global + the topmost context in the stack.
|
||||
// Only called from OnInit() (explicit activation) and OnEnable() (re-enable after disable).
|
||||
// The stack remembers context history for Pop restoration; only the current (topmost)
|
||||
// context is active at any time, preventing overlapping bindings from stacked maps.
|
||||
private void ApplyContextState()
|
||||
{
|
||||
if (_runtimeActions == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_runtimeActions.Disable();
|
||||
|
||||
if (_actionMaps.TryGetValue(InputContextId.Global, out InputActionMap globalMap))
|
||||
{
|
||||
globalMap.Enable();
|
||||
}
|
||||
|
||||
InputContextId current = _contextStack.Current;
|
||||
if (current != InputContextId.None && _actionMaps.TryGetValue(current, out InputActionMap currentMap))
|
||||
{
|
||||
currentMap.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyContextChanged()
|
||||
{
|
||||
ContextChanged?.Invoke(CurrentContext);
|
||||
}
|
||||
|
||||
private void OnActionTriggered(InputAction.CallbackContext context)
|
||||
{
|
||||
if (_activeRebindOperation != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InputActionId actionId = ParseActionId(context);
|
||||
InputContextId contextId = ParseContextId(context);
|
||||
InputDeviceKind deviceKind = UpdateDeviceKind(context);
|
||||
|
||||
InputCommand command = CreateCommand(actionId, contextId, deviceKind, context);
|
||||
|
||||
CommandTriggered?.Invoke(command);
|
||||
|
||||
if (_listeners.TryGetValue(actionId, out Action<InputCommand> listener))
|
||||
{
|
||||
listener?.Invoke(command);
|
||||
}
|
||||
}
|
||||
|
||||
private InputDeviceKind UpdateDeviceKind(InputAction.CallbackContext context)
|
||||
{
|
||||
InputDeviceKind deviceKind = InputDeviceKindUtility.FromDevice(context.control?.device);
|
||||
if (deviceKind != InputDeviceKind.Unknown && deviceKind != CurrentDeviceKind)
|
||||
{
|
||||
InputDeviceKind previous = CurrentDeviceKind;
|
||||
CurrentDeviceKind = deviceKind;
|
||||
Log.Debug("[InputModule] Device changed: {0} -> {1}", previous, deviceKind);
|
||||
DeviceKindChanged?.Invoke(CurrentDeviceKind);
|
||||
}
|
||||
|
||||
return CurrentDeviceKind;
|
||||
}
|
||||
|
||||
private static InputCommand CreateCommand(
|
||||
InputActionId actionId,
|
||||
InputContextId contextId,
|
||||
InputDeviceKind deviceKind,
|
||||
InputAction.CallbackContext context)
|
||||
{
|
||||
Vector2 vector2Value = Vector2.zero;
|
||||
float scalarValue = 0f;
|
||||
|
||||
if (context.action.expectedControlType == "Vector2")
|
||||
{
|
||||
vector2Value = context.ReadValue<Vector2>();
|
||||
scalarValue = vector2Value.magnitude;
|
||||
}
|
||||
else
|
||||
{
|
||||
scalarValue = context.ReadValue<float>();
|
||||
}
|
||||
|
||||
return new InputCommand(
|
||||
actionId,
|
||||
contextId,
|
||||
ConvertPhase(context.phase),
|
||||
deviceKind,
|
||||
vector2Value,
|
||||
scalarValue,
|
||||
context.time);
|
||||
}
|
||||
|
||||
private SettingComponent GetSettingComponent()
|
||||
{
|
||||
if (_settingComponent == null)
|
||||
{
|
||||
_settingComponent = UnityGameFramework.Runtime.GameEntry.GetComponent<SettingComponent>();
|
||||
}
|
||||
|
||||
return _settingComponent;
|
||||
}
|
||||
|
||||
private static InputActionId ParseActionId(InputAction.CallbackContext context)
|
||||
{
|
||||
return Enum.TryParse(context.action.name, out InputActionId actionId)
|
||||
? actionId
|
||||
: InputActionId.Unknown;
|
||||
}
|
||||
|
||||
private static InputContextId ParseContextId(InputAction.CallbackContext context)
|
||||
{
|
||||
return TryParseContextId(context.action.actionMap.name, out InputContextId contextId)
|
||||
? contextId
|
||||
: InputContextId.None;
|
||||
}
|
||||
|
||||
private static bool TryParseContextId(string rawContextId, out InputContextId contextId)
|
||||
{
|
||||
return Enum.TryParse(rawContextId, out contextId);
|
||||
}
|
||||
|
||||
private static InputCommandPhase ConvertPhase(InputActionPhase phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case InputActionPhase.Started:
|
||||
return InputCommandPhase.Started;
|
||||
case InputActionPhase.Canceled:
|
||||
return InputCommandPhase.Canceled;
|
||||
default:
|
||||
return InputCommandPhase.Performed;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetAction(InputContextId contextId, InputActionId actionId, out InputAction action)
|
||||
{
|
||||
action = null;
|
||||
if (!_actionMaps.TryGetValue(contextId, out InputActionMap map))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
action = map.FindAction(actionId.ToString());
|
||||
return action != null;
|
||||
}
|
||||
|
||||
private bool DetectConflict(InputAction action, int bindingIndex, string newPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
InputActionMap targetMap = action.actionMap;
|
||||
InputBinding targetBinding = action.bindings[bindingIndex];
|
||||
|
||||
foreach (InputActionMap map in _runtimeActions.actionMaps)
|
||||
{
|
||||
if (map != targetMap && map.name != nameof(InputContextId.Global))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (InputBinding binding in map.bindings)
|
||||
{
|
||||
if (map == targetMap && binding.id == targetBinding.id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(binding.effectivePath, newPath, System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private InputActionRebindingExtensions.RebindingOperation CreateRebindOperation(
|
||||
InputAction action,
|
||||
InputContextId contextId,
|
||||
InputActionId actionId,
|
||||
int bindingIndex,
|
||||
float timeoutSeconds,
|
||||
InputDeviceKind expectedDeviceKind,
|
||||
string previousEffectivePath,
|
||||
string previousOverridePath)
|
||||
{
|
||||
InputActionRebindingExtensions.RebindingOperation operation = new InputActionRebindingExtensions.RebindingOperation()
|
||||
.WithAction(action)
|
||||
.WithTargetBinding(bindingIndex)
|
||||
.WithTimeout(timeoutSeconds)
|
||||
.WithControlsExcluding("<Pointer>/delta")
|
||||
.WithControlsExcluding("<Pointer>/position")
|
||||
.WithControlsExcluding("<Touchscreen>/touch*/position")
|
||||
.WithControlsExcluding("<Touchscreen>/touch*/delta")
|
||||
.WithControlsExcluding("<Mouse>/clickCount")
|
||||
.WithMatchingEventsBeingSuppressed()
|
||||
.OnCancel(_ =>
|
||||
{
|
||||
RebindStatus status = _rebindCanceledByUser ? RebindStatus.Canceled : RebindStatus.Timeout;
|
||||
CompleteRebind(new RebindResult(status, contextId, actionId, bindingIndex, action.bindings[bindingIndex].effectivePath, previousEffectivePath));
|
||||
})
|
||||
.OnComplete(rebindOperation =>
|
||||
{
|
||||
InputDeviceKind actualDeviceKind = InputDeviceKindUtility.FromDevice(rebindOperation.selectedControl?.device);
|
||||
if (expectedDeviceKind != InputDeviceKind.Unknown && actualDeviceKind != expectedDeviceKind)
|
||||
{
|
||||
RevertBinding(action, bindingIndex, previousOverridePath);
|
||||
CompleteRebind(new RebindResult(RebindStatus.Canceled, contextId, actionId, bindingIndex, action.bindings[bindingIndex].effectivePath, previousEffectivePath));
|
||||
return;
|
||||
}
|
||||
|
||||
string newPath = action.bindings[bindingIndex].effectivePath;
|
||||
if (DetectConflict(action, bindingIndex, newPath))
|
||||
{
|
||||
RevertBinding(action, bindingIndex, previousOverridePath);
|
||||
CompleteRebind(new RebindResult(RebindStatus.ConflictDetected, contextId, actionId, bindingIndex, newPath, previousEffectivePath));
|
||||
return;
|
||||
}
|
||||
|
||||
CompleteRebind(new RebindResult(RebindStatus.Succeeded, contextId, actionId, bindingIndex, newPath, previousEffectivePath));
|
||||
});
|
||||
|
||||
if (operation.expectedControlType != "Button")
|
||||
{
|
||||
operation.WithCancelingThrough("<Keyboard>/escape");
|
||||
}
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
private void CompleteRebind(RebindResult result)
|
||||
{
|
||||
_activeRebindOperation?.Dispose();
|
||||
_activeRebindOperation = null;
|
||||
|
||||
if (_contextStack.Current == InputContextId.Rebind)
|
||||
{
|
||||
PopContext();
|
||||
}
|
||||
|
||||
RebindCompleted?.Invoke(result);
|
||||
}
|
||||
|
||||
private static void RevertBinding(InputAction action, int bindingIndex, string previousOverridePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(previousOverridePath))
|
||||
{
|
||||
action.RemoveBindingOverride(bindingIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
action.ApplyBindingOverride(bindingIndex, previousOverridePath);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DisableActionForRebind(InputAction action)
|
||||
{
|
||||
InputActionMap map = action.actionMap;
|
||||
if (map == null)
|
||||
return;
|
||||
|
||||
if (map.enabled)
|
||||
{
|
||||
map.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
// Map is already disabled but action.enabled may still report true
|
||||
// because map.Disable() bails early when m_Enabled is already false.
|
||||
// Force-sync by briefly enabling and re-disabling the map.
|
||||
if (action.enabled)
|
||||
{
|
||||
map.Enable();
|
||||
map.Disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 895365716cd1c724194968e895e434bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace SepCore.InputModule.Runtime
|
||||
{
|
||||
public static class InputModuleDefaultActions
|
||||
{
|
||||
public static InputActionAsset Create()
|
||||
{
|
||||
InputActionAsset asset = ScriptableObject.CreateInstance<InputActionAsset>();
|
||||
|
||||
InputActionMap globalMap = new InputActionMap(nameof(InputContextId.Global));
|
||||
AddPauseAction(globalMap);
|
||||
asset.AddActionMap(globalMap);
|
||||
|
||||
InputActionMap uiMap = new InputActionMap(nameof(InputContextId.UI));
|
||||
AddNavigateAction(uiMap, nameof(InputActionId.Navigate));
|
||||
AddButtonAction(uiMap, nameof(InputActionId.Confirm), "<Keyboard>/enter", "<Keyboard>/numpadEnter", "<Gamepad>/buttonSouth");
|
||||
AddButtonAction(uiMap, nameof(InputActionId.Cancel), "<Keyboard>/escape", "<Keyboard>/backspace", "<Gamepad>/buttonEast");
|
||||
asset.AddActionMap(uiMap);
|
||||
|
||||
InputActionMap gameplayExploreMap = new InputActionMap(nameof(InputContextId.GameplayExplore));
|
||||
AddNavigateAction(gameplayExploreMap, nameof(InputActionId.Move));
|
||||
AddButtonAction(gameplayExploreMap, nameof(InputActionId.Sprint), "<Keyboard>/leftShift", "<Gamepad>/leftStickPress");
|
||||
AddButtonAction(gameplayExploreMap, nameof(InputActionId.Interact), "<Keyboard>/e", "<Gamepad>/buttonSouth");
|
||||
asset.AddActionMap(gameplayExploreMap);
|
||||
|
||||
InputActionMap dialogMap = new InputActionMap(nameof(InputContextId.Dialog));
|
||||
AddNavigateAction(dialogMap, nameof(InputActionId.Navigate));
|
||||
AddButtonAction(dialogMap, nameof(InputActionId.Confirm), "<Keyboard>/enter", "<Keyboard>/numpadEnter", "<Gamepad>/buttonSouth");
|
||||
AddButtonAction(dialogMap, nameof(InputActionId.Cancel), "<Keyboard>/escape", "<Keyboard>/backspace", "<Gamepad>/buttonEast");
|
||||
asset.AddActionMap(dialogMap);
|
||||
|
||||
InputActionMap rebindMap = new InputActionMap(nameof(InputContextId.Rebind));
|
||||
asset.AddActionMap(rebindMap);
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
private static void AddPauseAction(InputActionMap map)
|
||||
{
|
||||
AddButtonAction(map, nameof(InputActionId.Pause), "<Keyboard>/escape", "<Gamepad>/start");
|
||||
}
|
||||
|
||||
private static void AddButtonAction(InputActionMap map, string actionName, params string[] bindings)
|
||||
{
|
||||
InputAction action = map.AddAction(actionName, InputActionType.Button);
|
||||
foreach (string binding in bindings)
|
||||
{
|
||||
action.AddBinding(binding);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddNavigateAction(InputActionMap map, string actionName)
|
||||
{
|
||||
InputAction action = map.AddAction(actionName, InputActionType.Value);
|
||||
action.expectedControlType = "Vector2";
|
||||
action.AddCompositeBinding("2DVector")
|
||||
.With("Up", "<Keyboard>/w")
|
||||
.With("Down", "<Keyboard>/s")
|
||||
.With("Left", "<Keyboard>/a")
|
||||
.With("Right", "<Keyboard>/d");
|
||||
action.AddCompositeBinding("2DVector")
|
||||
.With("Up", "<Keyboard>/upArrow")
|
||||
.With("Down", "<Keyboard>/downArrow")
|
||||
.With("Left", "<Keyboard>/leftArrow")
|
||||
.With("Right", "<Keyboard>/rightArrow");
|
||||
action.AddBinding("<Gamepad>/leftStick");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1bc975c14c639b4faecf539e338b313
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace SepCore.InputModule.Runtime
|
||||
{
|
||||
public sealed class InputModuleDefaultPromptMap : IInputPromptMap
|
||||
{
|
||||
private readonly InputActionAsset _asset;
|
||||
private readonly Dictionary<(InputActionId, InputDeviceKind), InputPrompt> _entries;
|
||||
|
||||
public InputModuleDefaultPromptMap(InputActionAsset asset = null)
|
||||
{
|
||||
_asset = asset;
|
||||
_entries = new Dictionary<(InputActionId, InputDeviceKind), InputPrompt>();
|
||||
PopulateKeyboardMouse();
|
||||
PopulateGamepad();
|
||||
PopulateTouch();
|
||||
}
|
||||
|
||||
public bool TryGetPrompt(InputActionId actionId, InputDeviceKind deviceKind, out InputPrompt prompt)
|
||||
{
|
||||
if (_asset != null && TryDeriveFromOverride(actionId, deviceKind, out prompt))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _entries.TryGetValue((actionId, deviceKind), out prompt);
|
||||
}
|
||||
|
||||
private bool TryDeriveFromOverride(InputActionId actionId, InputDeviceKind deviceKind, out InputPrompt prompt)
|
||||
{
|
||||
prompt = InputPrompt.None;
|
||||
|
||||
InputAction action = _asset.FindAction(actionId.ToString());
|
||||
if (action == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string devicePrefix = GetDevicePrefix(deviceKind);
|
||||
if (devicePrefix == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < action.bindings.Count; i++)
|
||||
{
|
||||
InputBinding binding = action.bindings[i];
|
||||
|
||||
if (binding.isComposite || binding.isPartOfComposite)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (binding.overridePath == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string effectivePath = binding.effectivePath;
|
||||
if (effectivePath != null && effectivePath.StartsWith(devicePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string label = InputControlPath.ToHumanReadableString(effectivePath);
|
||||
prompt = new InputPrompt(label);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string GetDevicePrefix(InputDeviceKind deviceKind)
|
||||
{
|
||||
switch (deviceKind)
|
||||
{
|
||||
case InputDeviceKind.KeyboardMouse: return "<Keyboard>";
|
||||
case InputDeviceKind.Gamepad: return "<Gamepad>";
|
||||
case InputDeviceKind.Touch: return "<Touchscreen>";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateKeyboardMouse()
|
||||
{
|
||||
Add(InputActionId.Pause, InputDeviceKind.KeyboardMouse, "Esc", "keyboard_esc");
|
||||
Add(InputActionId.Navigate, InputDeviceKind.KeyboardMouse, "WASD");
|
||||
Add(InputActionId.Confirm, InputDeviceKind.KeyboardMouse, "Enter", "keyboard_enter");
|
||||
Add(InputActionId.Cancel, InputDeviceKind.KeyboardMouse, "Esc", "keyboard_esc");
|
||||
Add(InputActionId.Move, InputDeviceKind.KeyboardMouse, "WASD");
|
||||
Add(InputActionId.Sprint, InputDeviceKind.KeyboardMouse, "L Shift", "keyboard_shift");
|
||||
Add(InputActionId.Interact, InputDeviceKind.KeyboardMouse, "E", "keyboard_e");
|
||||
}
|
||||
|
||||
private void PopulateGamepad()
|
||||
{
|
||||
Add(InputActionId.Pause, InputDeviceKind.Gamepad, "Menu", "gamepad_menu");
|
||||
Add(InputActionId.Navigate, InputDeviceKind.Gamepad, "L Stick", "gamepad_full_ls");
|
||||
Add(InputActionId.Confirm, InputDeviceKind.Gamepad, "A", "gamepad_a");
|
||||
Add(InputActionId.Cancel, InputDeviceKind.Gamepad, "B", "gamepad_b");
|
||||
Add(InputActionId.Move, InputDeviceKind.Gamepad, "L Stick", "gamepad_full_ls");
|
||||
Add(InputActionId.Sprint, InputDeviceKind.Gamepad, "L3", "gamepad_press_ls");
|
||||
Add(InputActionId.Interact, InputDeviceKind.Gamepad, "A", "gamepad_a");
|
||||
}
|
||||
|
||||
private void PopulateTouch()
|
||||
{
|
||||
Add(InputActionId.Pause, InputDeviceKind.Touch, "Pause");
|
||||
Add(InputActionId.Navigate, InputDeviceKind.Touch, "Swipe");
|
||||
Add(InputActionId.Confirm, InputDeviceKind.Touch, "Tap");
|
||||
Add(InputActionId.Cancel, InputDeviceKind.Touch, "Back");
|
||||
Add(InputActionId.Move, InputDeviceKind.Touch, "Joystick");
|
||||
Add(InputActionId.Interact, InputDeviceKind.Touch, "Tap");
|
||||
}
|
||||
|
||||
private void Add(InputActionId actionId, InputDeviceKind deviceKind, string textLabel, string spriteName = null)
|
||||
{
|
||||
_entries[(actionId, deviceKind)] = new InputPrompt(textLabel, spriteName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reference": "GUID:436e23dbdc31e7d4fb5c3f804548b2df"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b8c810f3fb3fd948afcf17809e9217c
|
||||
AssemblyDefinitionReferenceImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user