十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity自定义输入管理器:从原理到实现,构建灵活可控的游戏交互系统

Unity自定义输入管理器:从原理到实现,构建灵活可控的游戏交互系统 1. 项目概述为什么Unity开发者需要自定义Input Manager在Unity项目开发中处理玩家输入是游戏交互的基石。无论是移动角色、发射子弹还是打开菜单都离不开一套稳定、灵活且易于管理的输入系统。Unity自带的Input Manager输入管理器是许多开发者尤其是初学者和中小型项目最先接触的输入解决方案。它通过一个可视化的配置界面允许我们定义诸如“Horizontal”水平移动、“Jump”跳跃等虚拟输入轴Axis然后在代码中通过Input.GetAxis或Input.GetButton来获取输入状态。这套系统上手快对于键盘、鼠标和标准游戏手柄的支持开箱即用是快速原型开发的利器。然而随着项目规模扩大特别是需要支持多平台、多种输入设备如不同品牌手柄、移动端触屏、VR控制器或实现复杂的按键重绑定Rebinding功能时默认Input Manager的局限性就暴露无遗。它的配置分散在Project Settings中难以进行版本控制下的差异化配置其逻辑相对固化扩展性不足更重要的是它缺乏一个清晰、集中的代码抽象层导致输入逻辑容易散落在游戏的各个角落为后续维护和迭代埋下隐患。因此“自定义输入管理器”应运而生。它并非要完全抛弃Unity的底层输入API而是在其之上构建一个更高级的抽象层。核心目标是将输入设备的“物理信号”如A键按下、左摇杆偏移与游戏逻辑的“虚拟操作”如“跳跃”、“互动”解耦。通过自定义管理器我们可以实现输入配置的数据化、输入事件的集中派发、多输入源的优先级管理以及运行时动态重映射按键等高级功能。这就像为你的游戏搭建了一个专属的“交通指挥中心”所有外部输入信号都在这里被统一识别、翻译和调度再有序地分发给游戏内的各个系统使得代码更清晰、扩展更灵活、维护更轻松。2. 核心设计思路构建一个健壮的自定义输入系统一个优秀的自定义输入管理器其设计应遵循高内聚、低耦合的原则。我们不能仅仅是对Input.GetKeyDown(KeyCode.Space)这样的调用进行简单封装而是要构建一套完整的架构。下面我将拆解其核心设计思路。2.1 输入抽象层动作Action与绑定Binding这是自定义输入管理器的灵魂。我们需要定义两个核心概念输入动作Input Action代表游戏中的一个逻辑操作如“移动”、“跳跃”、“攻击”。它不关心这个操作是由键盘空格键、手柄A键还是屏幕上的一个虚拟按钮触发的。输入绑定Input Binding建立“输入动作”与“物理输入源”之间的映射关系。例如“跳跃”动作可以绑定到“键盘Space键”和“手柄South按钮通常是A/X键”。这种设计带来了巨大灵活性。当我们需要更换输入设备时只需修改绑定关系所有游戏逻辑代码监听“跳跃”动作完全无需改动。同时一个动作可以绑定多个输入源系统会自动处理优先级或合并输入。2.2 输入状态与事件驱动我们需要为每个输入动作定义其可能的状态这通常比简单的“按下/松开”更丰富。一个常见的状态机包括Started输入刚刚开始例如按键按下的那一帧。Performed输入正在执行例如按键持续按住或摇杆保持偏移。对于按键Performed可能在Started后的每一帧都触发对于摇杆它会持续报告当前向量值。Canceled输入被取消例如按键松开或摇杆回中。管理器内部需要持续轮询PollUnity的原始输入在Update或FixedUpdate中并根据绑定关系计算出每个输入动作的当前状态。然后采用事件Event或观察者模式通知所有订阅了该动作的模块。例如当“攻击”动作的状态变为Started时管理器会触发一个OnAttackStarted事件玩家的武器系统、动画系统、音效系统都可以独立订阅这个事件并做出反应彼此之间没有直接依赖。2.3 配置数据与运行时管理所有“动作-绑定”的映射关系应该被设计为可序列化的数据如ScriptableObject或JSON配置文件而不是硬编码在脚本里。这样做的好处是非程序员也可配置策划或设计师可以通过编辑器工具调整按键配置。易于管理多套配置可以轻松创建“键盘鼠标”、“Xbox手柄”、“PlayStation手柄”等不同的输入配置方案并在运行时动态切换。支持本地化与个性化玩家自定义的按键设置本质上就是创建或修改一套绑定配置数据。管理器在运行时加载这些配置数据并据此构建内部的映射表。同时它还需要负责处理输入设备的插拔检测、不同设备输入源的自动切换等。3. 实现详解从零搭建自定义InputManager理论讲完我们进入实战环节。我将带领你一步步实现一个功能相对完整、可用于实际项目的自定义输入管理器。我们将创建几个核心的C#脚本。3.1 定义核心数据结构首先创建InputAction.cs和InputBinding.cs来定义我们的数据模型。// InputAction.cs using System; using UnityEngine; [CreateAssetMenu(fileName NewInputAction, menuName Input System/Input Action)] public class InputAction : ScriptableObject { public string actionName; // 动作名称如Move, Jump public InputActionType type InputActionType.Button; // 动作类型 // 我们可以为这个动作定义一些事件供外部订阅 public event ActionInputActionContext OnStarted; public event ActionInputActionContext OnPerformed; public event ActionInputActionContext OnCanceled; // 内部方法由InputManager调用以触发事件 internal void TriggerStarted(InputActionContext context) OnStarted?.Invoke(context); internal void TriggerPerformed(InputActionContext context) OnPerformed?.Invoke(context); internal void TriggerCanceled(InputActionContext context) OnCanceled?.Invoke(context); } public enum InputActionType { Button, // 按钮类型如跳跃、攻击 Value // 数值类型如移动、视角通常对应摇杆或鼠标Delta } // 输入上下文包含触发此次事件的详细信息 public struct InputActionContext { public InputAction action; public float floatValue; // 对于Value类型如摇杆X值 public Vector2 vector2Value; // 对于Value类型如移动向量 public object control; // 可扩展记录是哪个具体物理控件触发的 }// InputBinding.cs using System; using UnityEngine; [Serializable] public class InputBinding { public InputAction action; // 关联的虚拟动作 public InputControlType controlType; // 控制类型键盘、鼠标按钮、鼠标移动、手柄按钮、手柄摇杆 public KeyCode keyCode; // 当controlType为Keyboard时使用 public int mouseButton; // 当controlType为MouseButton时使用 (0左键, 1右键, 2中键) public GamepadButton gamepadButton; // 当controlType为GamepadButton时使用 public GamepadAxis gamepadAxis; // 当controlType为GamepadAxis时使用 // 对于摇杆可能需要一个乘数因子来处理不同平台的正反方向 public float axisMultiplier 1.0f; // 死区设置对于摇杆输入非常重要避免微小抖动被误识别 public float deadZone 0.2f; } public enum InputControlType { Keyboard, MouseButton, MouseMovement, // 如Mouse X, Mouse Y GamepadButton, GamepadAxis } // 这里简化定义实际项目中可能需要更完善的手柄按钮和轴枚举 public enum GamepadButton { A, B, X, Y, Start, Back, LeftShoulder, RightShoulder, DPadUp, DPadDown, DPadLeft, DPadRight } public enum GamepadAxis { LeftStickX, LeftStickY, RightStickX, RightStickY, LeftTrigger, RightTrigger }3.2 构建核心管理器CustomInputManager这是系统的中枢以单例模式实现确保全局可访问。// CustomInputManager.cs using System.Collections.Generic; using UnityEngine; public class CustomInputManager : MonoBehaviour { public static CustomInputManager Instance { get; private set; } [SerializeField] private InputBindingCollection _bindingCollection; // 一个包含所有绑定的ScriptableObject private Dictionarystring, InputAction _actions new Dictionarystring, InputAction(); private DictionaryInputAction, ListInputBinding _actionToBindings new DictionaryInputAction, ListInputBinding(); // 当前激活的输入设备类型用于多设备优先级管理 private InputDeviceType _activeDevice InputDeviceType.KeyboardAndMouse; void Awake() { if (Instance ! null Instance ! this) { Destroy(this.gameObject); return; } Instance this; DontDestroyOnLoad(this.gameObject); // 通常希望输入管理器跨场景存在 InitializeInputSystem(); } void InitializeInputSystem() { if (_bindingCollection null) { Debug.LogError(InputBindingCollection is not assigned!); return; } // 1. 注册所有InputAction foreach (var action in _bindingCollection.inputActions) { if (!_actions.ContainsKey(action.actionName)) { _actions.Add(action.actionName, action); _actionToBindings.Add(action, new ListInputBinding()); } } // 2. 建立动作与绑定的映射 foreach (var binding in _bindingCollection.bindings) { if (binding.action ! null _actionToBindings.ContainsKey(binding.action)) { _actionToBindings[binding.action].Add(binding); } } Debug.Log($Input System Initialized with {_actions.Count} actions and {_bindingCollection.bindings.Count} bindings.); } void Update() { // 每帧轮询所有绑定的输入 PollInputs(); // 可以在这里加入设备检测逻辑更新_activeDevice DetectActiveDevice(); } void PollInputs() { foreach (var kvp in _actionToBindings) { InputAction action kvp.Key; ListInputBinding bindings kvp.Value; bool wasPerformedThisFrame false; InputActionContext context new InputActionContext { action action }; foreach (var binding in bindings) { // 根据设备优先级可能跳过某些绑定的检测 if (!IsBindingRelevantForActiveDevice(binding)) continue; float rawValue 0f; bool isControlActive false; // 根据binding的类型检测输入 switch (binding.controlType) { case InputControlType.Keyboard: isControlActive Input.GetKey(binding.keyCode); rawValue isControlActive ? 1.0f : 0.0f; // 对于按钮我们更关心按下和松开瞬间 if (Input.GetKeyDown(binding.keyCode)) { action.TriggerStarted(context); } if (Input.GetKeyUp(binding.keyCode)) { action.TriggerCanceled(context); } break; case InputControlType.GamepadAxis: rawValue GetGamepadAxisValue(binding.gamepadAxis); // 应用死区 if (Mathf.Abs(rawValue) binding.deadZone) rawValue 0f; rawValue * binding.axisMultiplier; context.floatValue rawValue; // 对于摇杆我们通常只关心Performed状态并持续传递数值 if (Mathf.Abs(rawValue) 0.01f) { isControlActive true; action.TriggerPerformed(context); // 每帧有输入就触发Performed } break; // 其他类型如MouseButton, GamepadButton的实现类似... } if (isControlActive) { wasPerformedThisFrame true; // 对于Value类动作可以在这里更新context的vector2Value例如合并左右摇杆 } } // 对于Button类动作如果本帧没有任何绑定被激活但上一帧有则触发Canceled // 这部分逻辑需要记录上一帧的状态这里为简化未完全展开 } } private float GetGamepadAxisValue(GamepadAxis axis) { // 这里使用Unity旧的Input Manager API作为示例实际新项目推荐使用Input System Package string axisName ; switch (axis) { case GamepadAxis.LeftStickX: axisName Horizontal; break; // 假设在Unity Input Manager中已配置 case GamepadAxis.LeftStickY: axisName Vertical; break; case GamepadAxis.RightStickX: axisName Mouse X; break; // 注意这通常不是手柄右摇杆的标准映射 case GamepadAxis.RightStickY: axisName Mouse Y; break; } return string.IsNullOrEmpty(axisName) ? 0f : Input.GetAxis(axisName); } private bool IsBindingRelevantForActiveDevice(InputBinding binding) { // 简单的设备过滤逻辑 switch (_activeDevice) { case InputDeviceType.KeyboardAndMouse: return binding.controlType InputControlType.Keyboard || binding.controlType InputControlType.MouseButton || binding.controlType InputControlType.MouseMovement; case InputDeviceType.Gamepad: return binding.controlType InputControlType.GamepadButton || binding.controlType InputControlType.GamepadAxis; default: return true; } } private void DetectActiveDevice() { // 检测是否有手柄输入有则切换到手柄模式 if (Mathf.Abs(Input.GetAxis(Horizontal)) 0.1f || Mathf.Abs(Input.GetAxis(Vertical)) 0.1f || Input.GetKeyDown(KeyCode.JoystickButton0)) { _activeDevice InputDeviceType.Gamepad; } // 检测是否有键盘鼠标输入有则切换 else if (Input.anyKeyDown || Mathf.Abs(Input.GetAxis(Mouse X)) 0f) { _activeDevice InputDeviceType.KeyboardAndMouse; } } // 供外部代码调用的便捷方法 public static bool GetActionButton(string actionName) { if (Instance._actions.TryGetValue(actionName, out InputAction action)) { // 这里需要访问该动作当前是否处于“执行中”状态 // 简化处理遍历其绑定检查是否有对应键被按住 // 实际应有更完善的状态机记录 var bindings Instance._actionToBindings[action]; foreach(var b in bindings) { if (b.controlType InputControlType.Keyboard Input.GetKey(b.keyCode)) return true; } } return false; } public static float GetActionAxis(string actionName) { /* 类似实现 */ } } public enum InputDeviceType { KeyboardAndMouse, Gamepad }3.3 创建配置资产InputBindingCollection为了在Inspector中方便地编辑所有绑定我们创建一个InputBindingCollectionScriptableObject。// InputBindingCollection.cs using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(fileName InputBindings, menuName Input System/Input Binding Collection)] public class InputBindingCollection : ScriptableObject { public ListInputAction inputActions new ListInputAction(); public ListInputBinding bindings new ListInputBinding(); }在Unity编辑器中右键Create菜单即可创建这个资产。然后将CustomInputManager脚本挂载到一个GameObject上例如_GameManager并将创建好的InputBindingCollection资产拖拽赋值。接下来你就可以在这个Collection资产中创建InputAction如JumpAction并在Bindings列表中添加新的绑定将JumpAction关联到KeyCode.Space和GamepadButton.A。3.4 在游戏中使用自定义输入现在游戏中的其他系统可以摆脱对具体键位的依赖转而监听我们定义的虚拟动作。// PlayerController.cs using UnityEngine; public class PlayerController : MonoBehaviour { [SerializeField] private float moveSpeed 5f; [SerializeField] private float jumpForce 10f; [SerializeField] private InputAction moveAction; // 在Inspector中拖入定义好的Move动作资产 [SerializeField] private InputAction jumpAction; // 拖入Jump动作资产 private Rigidbody rb; private Vector2 moveInput; void Start() { rb GetComponentRigidbody(); // 订阅输入事件 if (moveAction ! null) { moveAction.OnPerformed OnMovePerformed; moveAction.OnCanceled OnMoveCanceled; } if (jumpAction ! null) { jumpAction.OnStarted OnJumpStarted; } } void OnDestroy() { // 务必取消订阅防止内存泄漏 if (moveAction ! null) { moveAction.OnPerformed - OnMovePerformed; moveAction.OnCanceled - OnMoveCanceled; } if (jumpAction ! null) { jumpAction.OnStarted - OnJumpStarted; } } void OnMovePerformed(InputActionContext ctx) { // ctx.vector2Value 应该包含来自摇杆或WASD的输入向量 moveInput ctx.vector2Value; } void OnMoveCanceled(InputActionContext ctx) { moveInput Vector2.zero; } void OnJumpStarted(InputActionContext ctx) { if (Physics.Raycast(transform.position, Vector3.down, 1.1f)) { rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); } } void FixedUpdate() { // 使用缓存的输入向量进行移动避免在FixedUpdate中频繁查询输入 Vector3 movement new Vector3(moveInput.x, 0, moveInput.y) * moveSpeed * Time.fixedDeltaTime; rb.MovePosition(rb.position transform.TransformDirection(movement)); } }提示将InputActionScriptableObject直接拖到PlayerController的Inspector中进行赋值是一种清晰且松耦合的依赖注入方式。这使得同一个动作如“跳跃”可以被玩家、UI、录像回放系统等多个组件共享和监听而它们彼此不知情。4. 高级功能扩展与实战技巧基础框架搭建完成后我们可以为其注入更多实用功能使其成为一个真正强大的生产级工具。4.1 实现运行时按键重绑定这是玩家设置中的常见需求。核心思路是提供一个界面当玩家点击“重新绑定”按钮时系统进入“监听模式”等待玩家按下下一个按键或摇动摇杆然后捕获这个输入并更新对应InputBinding中的数据。// 在CustomInputManager中添加 private InputBinding _pendingRebinding; // 等待重绑定的目标 private System.ActionInputBinding _onRebindComplete; public void StartRebind(InputBinding targetBinding, System.ActionInputBinding callback) { _pendingRebinding targetBinding; _onRebindComplete callback; StartCoroutine(RebindCoroutine()); } private System.Collections.IEnumerator RebindCoroutine() { // 禁用常规输入处理避免干扰 // 显示“请按下新按键...”的UI提示 while (_pendingRebinding ! null) { // 1. 检测键盘按键 if (Input.anyKeyDown) { foreach(KeyCode keyCode in System.Enum.GetValues(typeof(KeyCode))) { if (Input.GetKeyDown(keyCode)) { _pendingRebinding.controlType InputControlType.Keyboard; _pendingRebinding.keyCode keyCode; FinishRebind(); yield break; } } } // 2. 检测手柄按钮需要遍历所有可能的手柄按钮 // 3. 检测手柄摇杆需要设置一个阈值和超时防止误触 yield return null; // 每帧检测 } } private void FinishRebind() { _onRebindComplete?.Invoke(_pendingRebinding); // 保存配置到磁盘如PlayerPrefs或JSON文件 SaveBindingsToFile(); _pendingRebinding null; _onRebindComplete null; }4.2 输入组合键与长按检测自定义管理器可以轻松实现组合键如CtrlS和长按判定这些在默认Input Manager中实现起来比较麻烦。// 可以在InputBinding中增加组合键字段 [Serializable] public class InputBinding { // ... 原有字段 ... public ListKeyCode modifiers; // 修饰键列表如KeyCode.LeftControl public float holdTimeRequired 0f; // 需要按住的时间用于长按检测 private float _currentHoldTime 0f; } // 在PollInputs的检测逻辑中加入修饰键判断 bool modifiersSatisfied true; foreach (var mod in binding.modifiers) { if (!Input.GetKey(mod)) { modifiersSatisfied false; break; } } if (!modifiersSatisfied) continue; // 修饰键不满足跳过该绑定 // 长按检测 if (binding.holdTimeRequired 0) { if (isControlActiveThisFrame) { binding._currentHoldTime Time.deltaTime; if (binding._currentHoldTime binding.holdTimeRequired) { // 触发长按事件 action.TriggerPerformed(context); } } else { binding._currentHoldTime 0f; } }4.3 输入缓冲Input Buffer与连招系统在动作游戏中输入缓冲允许玩家在动作结束前提前输入下一个指令使连招更流畅。管理器可以维护一个短暂的输入缓冲区。public class InputBuffer { private struct BufferedInput { public InputAction action; public float bufferTime; public float expireTime; } private ListBufferedInput _buffer new ListBufferedInput(); public float defaultBufferWindow 0.2f; // 默认200ms缓冲窗口 public void BufferInput(InputAction action, float customWindow -1) { float window customWindow 0 ? customWindow : defaultBufferWindow; _buffer.Add(new BufferedInput { action action, bufferTime window, expireTime Time.time window }); // 清理过期输入 _buffer.RemoveAll(i Time.time i.expireTime); } public bool ConsumeBufferedInput(InputAction action) { for (int i 0; i _buffer.Count; i) { if (_buffer[i].action action) { _buffer.RemoveAt(i); return true; } } return false; } }在玩家按下攻击键时调用BufferInput(attackAction)。在角色可接受下一个攻击指令的状态如上一段攻击的恢复期结束时检查缓冲区ConsumeBufferedInput(attackAction)如果存在则立即触发下一次攻击。4.4 与Unity新Input System的集成Unity推出了全新的Input System包功能更强大、跨平台支持更好。我们的自定义管理器可以作为一层适配器或业务逻辑层底层调用新的Input System。策略将InputBinding中的controlType和具体键位映射到新Input System中的InputActionReference。优势既利用了新系统对触屏、手柄陀螺仪等现代设备的原生支持又保持了项目上层代码游戏逻辑的稳定无需大规模重写。方法在CustomInputManager的PollInputs方法中不再使用Input.GetKey而是读取新Input System中PlayerInput组件生成的InputAction的值和状态。5. 常见问题、调试技巧与性能优化在实际使用自定义输入管理器的过程中你一定会遇到各种问题。以下是我从项目中总结出的经验。5.1 常见问题排查表问题现象可能原因排查步骤与解决方案输入完全无响应1.CustomInputManager实例未正确初始化或未设为单例。2.InputBindingCollection资产未赋值。3. 游戏对象未订阅动作事件。1. 检查场景中是否存在CustomInputManager的GameObject并确认Awake方法被调用。2. 在Inspector中确认_bindingCollection字段已拖入资产。3. 在PlayerController等脚本的Start方法中打日志确认订阅成功。部分按键无效1. 绑定配置错误如KeyCode拼写错误。2. 设备类型过滤逻辑错误当前激活设备与绑定不匹配。3. 死区DeadZone设置过大微小输入被忽略。1. 在InputBindingCollection资产中仔细检查每个绑定的KeyCode或GamepadButton。2. 在DetectActiveDevice方法中打印_activeDevice日志确认切换逻辑正确。3. 对于摇杆将deadZone暂时设为0进行测试。输入有延迟或粘滞1. 输入检测写在FixedUpdate中但渲染帧率远高于物理帧率导致输入采样丢失。2. 事件触发逻辑有误Performed状态未正确结束。1.黄金法则输入检测必须放在Update中。可以在Update中捕获输入并存储状态在FixedUpdate中使用该状态。2. 检查PollInputs中对于按钮松开GetKeyUp和摇杆回中值小于死区时是否正确触发了Canceled事件。多设备同时操作冲突1. 未处理多设备输入优先级导致键盘和手柄输入互相干扰。1. 完善IsBindingRelevantForActiveDevice逻辑或在PollInputs中为每个动作只取优先级最高的有效输入源。按键重绑定后不保存1. 序列化保存逻辑未实现或路径错误。2. 运行时修改的是ScriptableObject实例但未标记为脏或未调用SaveAssets仅编辑器模式。1. 实现SaveBindingsToFile方法使用JsonUtility.ToJson将绑定列表序列化为JSON并用File.WriteAllText保存到Application.persistentDataPath。2. 游戏启动时从该路径加载JSON并覆盖默认绑定。5.2 调试技巧可视化输入状态在开发期创建一个简单的屏幕GUI来实时显示所有输入动作的状态和原始值是极其有效的调试手段。// DebugInputUI.cs using UnityEngine; public class DebugInputUI : MonoBehaviour { void OnGUI() { GUILayout.BeginArea(new Rect(10, 10, 300, Screen.height - 20)); GUILayout.Label( 输入状态调试 ); foreach (var action in CustomInputManager.Instance.GetAllActions()) // 需要在Manager中实现GetAllActions方法 { GUILayout.BeginHorizontal(); GUILayout.Label(${action.actionName}:, GUILayout.Width(100)); // 这里假设我们有一个方法能获取动作的当前“值”对于按钮是0/1对于摇杆是向量 float value CustomInputManager.Instance.GetActionAxis(action.actionName); GUILayout.HorizontalSlider(value, -1, 1, GUILayout.Width(150)); GUILayout.EndHorizontal(); } GUILayout.Label($当前活跃设备: {CustomInputManager.Instance.ActiveDevice}); GUILayout.EndArea(); } }5.3 性能优化要点输入管理器每帧都要执行虽不复杂但优化仍有必要避免GC分配在Update循环中避免使用foreach遍历可变集合如ListT改为for循环。InputActionContext这类结构体尽量复用不要每帧新建。减少不必要的轮询如果某个输入动作在当前场景或游戏状态下根本不会被使用例如菜单界面下的“攻击”键可以在管理器中动态禁用对其绑定的检测。使用静态委托对于高频触发的输入事件如每帧的移动Performed如果订阅者很多事件调用会有开销。可以考虑让关键的、性能敏感的组件如角色移动直接通过管理器提供的静态方法如GetActionAxis在FixedUpdate中查询状态而非依赖事件。配置热重载在编辑器模式下可以监听InputBindingCollection资产的更改实现按键配置的实时热重载无需重启游戏即可测试大幅提升迭代效率。5.4 一个真实的“坑”ScriptableObject的实例化陷阱这是我早期踩过的一个大坑。InputAction被设计为ScriptableObject资产。如果你在多个玩家或敌人Prefab中都直接引用同一个InputAction资产并为它订阅事件那么所有Prefab都会收到彼此触发的事件这显然不是我们想要的。解决方案对于需要独立输入状态的实体如分屏游戏中的两个玩家不应该直接共享同一个ScriptableObject资产。有两种方法运行时实例化在Start或Awake中使用ScriptableObject.CreateInstanceInputAction()为每个实体创建独立的实例然后从主配置资产拷贝名称、类型等信息。使用标识符而非引用在实体脚本中只存储动作的名称字符串如”Jump”。在输入管理器中维护一个从动作名到共享动作资产的字典。实体通过名称向管理器查询并订阅事件。管理器内部需要更复杂的事件路由逻辑确保不同实体能区分开属于自己的输入事件。通常这需要为事件回调增加一个“来源”参数。对于大多数单玩家游戏所有逻辑共享同一套输入状态是没问题的直接引用资产是最简单的方式。但了解这个陷阱能在你设计多人或复杂AI输入时避免很多头疼的问题。
返回列表