Unity3D 输入系统(Input Manager/新Input System事件与轴映射)

举报
William 发表于 2026/01/09 10:14:40 2026/01/09
【摘要】 一、引言与技术背景在游戏开发中,输入处理是与玩家交互的桥梁,是游戏逻辑的起点。自 Unity 诞生以来,其输入系统经历了一次重大的演进,旨在解决传统系统在灵活性、多平台和现代硬件支持方面的不足。传统 Input Manager:自 Unity 早期版本起便存在,通过在 Edit -> Project Settings -> Input Manager中配置 axes来工作。它简单易用,对于基...


一、引言与技术背景

在游戏开发中,输入处理是与玩家交互的桥梁,是游戏逻辑的起点。自 Unity 诞生以来,其输入系统经历了一次重大的演进,旨在解决传统系统在灵活性、多平台和现代硬件支持方面的不足。
  • 传统 Input Manager:自 Unity 早期版本起便存在,通过在 Edit -> Project Settings -> Input Manager中配置 axes来工作。它简单易用,对于基础的键盘、鼠标和手柄输入处理绰绰有余,但其基于字符串的配置轮询式(Polling)​ 的 API 以及有限的动作抽象使其在面对复杂输入需求时显得力不从心。
  • 新一代 Input System:随着游戏形态的多样化(如触屏、VR/AR、多设备协同)和对开发效率要求的提高,Unity 推出了全新的 Input System 包。它采用事件驱动基于动作(Action)​ 的现代设计理念,支持跨平台配置文件更丰富的设备支持更强的可扩展性,代表了 Unity 输入处理的未来。
理解这两套系统的工作原理、优缺点以及如何在实际项目中应用它们,是成为一名成熟 Unity 开发者的关键一步。

二、核心概念与原理

1. 传统 Input Manager

  • 工作原理:基于一个在编辑器中预设的、静态的轴向(Axes)列表。每个轴向定义了输入的名称、类型(如 Key、Mouse Button、Joystick Axis)、正负向按键、灵敏度等信息。
  • API 风格轮询式(Polling)。开发者需要在 Update()等函数中主动、不断地调用 Input.GetKey(), Input.GetAxis()等函数来查询当前的输入状态。
  • 核心函数
    • Input.GetKey(KeyCode.A): 查询指定按键是否正在被按住
    • Input.GetKeyDown(KeyCode.Space): 查询指定按键是否在本帧被按下
    • Input.GetAxis("Horizontal"): 查询一个轴向的值(如 W/S 或 左摇杆X轴),返回值在 -1 到 1 之间,带有平滑滤波。
    • Input.GetButtonDown("Jump"): 查询一个在 Input Manager 中定义的“按钮”动作是否在本帧被触发。

2. 新一代 Input System

  • 核心概念
    • Actions(动作):抽象的、有意义的输入意图,如“移动”、“跳跃”、“射击”。一个动作可以绑定到一个或多个输入源的多个控件(Controls)上。
    • Bindings(绑定):将动作与具体的输入控件(如 <Keyboard>/a<Gamepad>/buttonSouth)连接起来的规则。支持组合绑定(如 W/S 或 左摇杆Y轴 都可控制“移动”的垂直分量)。
    • Devices(设备):输入源,如 Keyboard、Mouse、Gamepad、Touchscreen 等。
    • Control Schemes(控制方案):将一组相关的 Actions 和 Bindings 组织在一起,用于快速切换(如“键盘鼠标”方案和“手柄”方案)。
    • Interaction(交互):定义了对一个动作的触发方式,如 Press(按下即触发)、Hold(按住一段时间后触发)、Tap(快速点击触发)、MultiTap(多次快速点击触发)。这极大地丰富了输入语义。
    • Processors(处理器):在输入值被传递前对其进行处理,如 Invert(反转)、Scale(缩放)、Smoothing(平滑)。
  • API 风格事件驱动(Event-driven)​ 与 回调(Callback)​ 相结合,同时也保留了轮询的能力。开发者可以为一个动作注册回调函数,当输入发生时自动调用,实现了逻辑与检测的分离。

3. 原理流程图

传统 Input Manager (Polling):
[Player presses 'A' key]
      |
      V
[每一帧 Update() is called]
      |
      V
[Your Code: if (Input.GetKey(KeyCode.A))]
      |-----------------------------|
      |                             |
  [Condition is TRUE]            [Condition is FALSE]
      |                             |
[Execute Movement Logic]         [Do Nothing for this frame]
新 Input System (Event-driven):
[Player presses 'A' key]
      |
      V
[Input System detects event]
      |
      V
[Matches binding for "Move" action]
      |
      V
[Triggers "performed" callback for "Move"]
      |
      V
[Your Callback Function: OnMovePerformed(context)]
      |
      V
[Read processed value from context and execute logic]

三、应用使用场景

场景
传统 Input Manager
新一代 Input System
理由
快速原型/简单2D游戏
✅ 首选
✅ 可用
IM 开箱即用,配置简单,足以满足需求。
现代3D/主机级游戏
❌ 不推荐
首选
IS 对复杂操作、手柄震动、玩家设置等支持更好。
多平台发布(PC/Console/Mobile)
❌ 繁琐
首选
IS 的控制方案可轻松切换不同平台的控制方式。
VR/AR 应用
❌ 不支持
唯一选择
IS 原生支持 XR 控制器、手势追踪等复杂设备。
需要玩家自定义按键
❌ 极其困难
内置支持
IS 允许在运行时动态修改绑定,是实现按键设置的理想方案。
复杂的输入交互(如蓄力攻击)
❌ 需手动实现
内置支持
IS 的 HoldInteraction 可直接实现,无需计时器代码。

四、环境准备

  • Unity 版本:2019.4 LTS 或更高。
  • 传统 Input Manager:默认启用,无需额外安装。
  • 新一代 Input System
    1. 打开 Unity Package Manager (Window -> Package Manager)。
    2. 点击左上角的 +号,选择 Add package by name...
    3. 输入 com.unity.inputsystem并点击 Add
    4. 添加后,Unity 会提示重启编辑器以启用新输入系统。点击 Yes。重启后,在 Edit -> Project Settings -> Player -> Other Settings​ 中,确认 Active Input Handling​ 已设置为 Input System Package (New)​ 或 Both(为了兼容旧代码)。

五、不同场景的代码实现

我们将创建两个场景来分别演示两套系统。

场景一:传统 Input Manager 实现角色移动

1. 配置 Input Manager
  • 打开 Edit -> Project Settings -> Input Manager
  • 展开 Axes列表,可以看到预设的 HorizontalVertical轴。它们已经配置好了 WASD 和方向键的映射。我们直接使用它们。
2. 角色移动脚本:PlayerMovement_IM.cs
using UnityEngine;

/// <summary>
/// 使用传统 Input Manager 实现的玩家移动脚本
/// </summary>
public class PlayerMovement_IM : MonoBehaviour
{
    [Header("Movement Settings")]
    public float moveSpeed = 5f;

    private Rigidbody rb;
    private Vector3 movement;

    void Start()
    {
        // 获取或添加 Rigidbody 组件
        rb = GetComponent<Rigidbody>();
        if (rb == null)
        {
            rb = gameObject.AddComponent<Rigidbody>();
            rb.useGravity = false; // 为了简化,在平面上不使用重力
            rb.constraints = RigidbodyConstraints.FreezeRotation; // 冻结旋转
        }
    }

    void Update()
    {
        // 1. 轮询 Input Manager 获取轴向输入
        // GetAxis 返回一个平滑的浮点数 (-1 到 1)
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        // 2. 计算移动向量
        // 注意:Input.GetAxis("Horizontal") 对应 X 轴,("Vertical") 对应 Z 轴
        movement = new Vector3(horizontalInput, 0f, verticalInput);

        // 可选:在 Update 中处理跳跃等非物理移动
        // if (Input.GetButtonDown("Jump"))
        // {
        //     // Jump logic
        // }
    }

    void FixedUpdate()
    {
        // 3. 在 FixedUpdate 中应用物理移动
        // 将输入向量从本地空间转换到世界空间,然后乘以速度和时间
        Vector3 moveVelocity = transform.TransformDirection(movement) * moveSpeed;
        rb.velocity = new Vector3(moveVelocity.x, rb.velocity.y, moveVelocity.z);
    }
}
使用:将此脚本挂载到一个带有 Collider 的 GameObject(如 Capsule)上,运行游戏,使用 WASD 键即可控制其移动。

场景二:新一代 Input System 实现角色移动与交互

1. 创建 Input Actions Asset
  • 在 Project 视图中右键 -> Create -> Input Actions,命名为 PlayerInputActions
  • 双击打开它。
  • 点击 Actions​ 旁边的 +号,创建一个 Action Map,命名为 Gameplay
  • Gameplay下,创建三个 Actions:
    • Move: Type 默认为 Value, Control Type 为 Vector2。这是用于处理摇杆或 WASD 移动的完美类型。
    • Look: Type 为 Value, Control Type 为 Vector2
    • Jump: Type 为 Button
2. 配置 Bindings
  • Move:
    • 选中 Move动作,在右侧的 Bindings 面板点击 +-> Add Binding
    • Path 旁边点击垃圾桶图标旁的菜单,依次添加:
      • <Keyboard>/w(Path: <Keyboard>/w, Value: 1, Part: Y)
      • <Keyboard>/s(Path: <Keyboard>/s, Value: -1, Part: Y)
      • <Keyboard>/a(Path: <Keyboard>/a, Value: -1, Part: X)
      • <Keyboard>/d(Path: <Keyboard>/d, Value: 1, Part: X)
    • 勾选 Keyboard绑定的 Composite 2D Vector​ 旁边的复选框,Unity 会自动为你组合这些按键。
    • 再添加一个新的 Binding (+),Path 选择 <Gamepad>/leftStick。现在 WASD 和手柄左摇杆都能控制 Move 动作。
  • Jump:
    • 选中 Jump动作,添加两个 Bindings:
      • <Keyboard>/space
      • <Gamepad>/buttonSouth(通常是 A/X 键)
  • Look: (为后续做准备)
    • 添加 <Mouse>/delta<Gamepad>/rightStick绑定。
3. 生成 C# 类(可选但推荐)
  • 在 Input Actions Asset 的工具栏上,点击 Generate C# Class。这会在你的项目中生成一个强类型的 C# 包装类,方便引用。
4. 玩家移动脚本(事件驱动):PlayerMovement_IS.cs
using UnityEngine;
using UnityEngine.InputSystem;

/// <summary>
/// 使用新一代 Input System (事件驱动) 实现的玩家移动脚本
/// </summary>
public class PlayerMovement_IS : MonoBehaviour
{
    [Header("Movement Settings")]
    public float moveSpeed = 5f;

    private Rigidbody rb;
    private Vector2 moveInput; // 从 Input System 的 Vector2 动作接收输入
    private PlayerInputActions inputActions; // 引用自动生成的 Input Actions 类

    void Awake()
    {
        rb = GetComponent<Rigidbody>();
        if (rb == null)
        {
            rb = gameObject.AddComponent<Rigidbody>();
            rb.useGravity = false;
            rb.constraints = RigidbodyConstraints.FreezeRotation;
        }

        // 实例化 Input Actions
        inputActions = new PlayerInputActions();

        // 为 Move 动作的 "performed" 和 "canceled" 事件注册回调
        // "started" 在输入开始时触发,"performed" 在值变化时触发,"canceled" 在输入释放时触发
        inputActions.Gameplay.Move.performed += OnMovePerformed;
        inputActions.Gameplay.Move.canceled += OnMoveCanceled;

        // 为 Jump 动作的按下和释放注册回调
        inputActions.Gameplay.Jump.performed += OnJumpPerformed;
        inputActions.Gameplay.Jump.canceled += OnJumpCanceled;
    }

    void OnEnable()
    {
        // 启用 Input Actions Map,开始监听输入
        inputActions.Gameplay.Enable();
    }

    void OnDisable()
    {
        // 禁用 Input Actions Map,停止监听输入
        inputActions.Gameplay.Disable();
    }

    // Move 动作被触发或值改变时调用
    void OnMovePerformed(InputAction.CallbackContext context)
    {
        // ReadValue<Vector2>() 直接获取 Vector2 类型的输入值
        moveInput = context.ReadValue<Vector2>();
    }

    // Move 动作被取消时调用 (如松开按键)
    void OnMoveCanceled(InputAction.CallbackContext context)
    {
        moveInput = Vector2.zero;
    }

    // Jump 动作按下时调用
    void OnJumpPerformed(InputAction.CallbackContext context)
    {
        Debug.Log("Jump Pressed! Performing jump logic...");
        // 在这里实现跳跃逻辑,例如给 Rigidbody 一个向上的冲量
        // rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
    }
    
    // Jump 动作释放时调用
    void OnJumpCanceled(InputAction.CallbackContext context)
    {
        Debug.Log("Jump Released!");
    }


    void FixedUpdate()
    {
        // 在物理更新中应用移动
        // moveInput 已经是世界空间的 XZ 平面向量
        Vector3 moveDirection = new Vector3(moveInput.x, 0, moveInput.y);
        Vector3 moveVelocity = moveDirection * moveSpeed;
        rb.velocity = new Vector3(moveVelocity.x, rb.velocity.y, moveVelocity.z);
    }

    // 脚本销毁时取消注册事件,防止内存泄漏
    void OnDestroy()
    {
        if (inputActions != null)
        {
            inputActions.Gameplay.Move.performed -= OnMovePerformed;
            inputActions.Gameplay.Move.canceled -= OnMoveCanceled;
            inputActions.Gameplay.Jump.performed -= OnJumpPerformed;
            inputActions.Gameplay.Jump.canceled -= OnJumpCanceled;
        }
    }
}
5. 玩家移动脚本(轮询):PlayerMovement_IS_Polling.cs
有时你仍然希望使用熟悉的轮询方式。IS 也支持这种方式。
using UnityEngine;
using UnityEngine.InputSystem;

/// <summary>
/// 使用新一代 Input System (轮询) 实现的玩家移动脚本
/// </summary>
public class PlayerMovement_IS_Polling : MonoBehaviour
{
    [Header("Movement Settings")]
    public float moveSpeed = 5f;

    private Rigidbody rb;
    private PlayerInputActions inputActions;

    void Awake()
    {
        rb = GetComponent<Rigidbody>();
        if (rb == null)
        {
            rb = gameObject.AddComponent<Rigidbody>();
            rb.useGravity = false;
            rb.constraints = RigidbodyConstraints.FreezeRotation;
        }
        inputActions = new PlayerInputActions();
    }

    void OnEnable()
    {
        inputActions.Gameplay.Enable();
    }

    void OnDisable()
    {
        inputActions.Gameplay.Disable();
    }

    void FixedUpdate()
    {
        // 使用 ReadValue 方法轮询当前输入值
        Vector2 moveInput = inputActions.Gameplay.Move.ReadValue<Vector2>();

        Vector3 moveDirection = new Vector3(moveInput.x, 0, moveInput.y);
        Vector3 moveVelocity = moveDirection * moveSpeed;
        rb.velocity = new Vector3(moveVelocity.x, rb.velocity.y, moveVelocity.z);

        // 轮询按钮
        // if (inputActions.Gameplay.Jump.triggered) // .triggered 检查本帧是否被触发
        // {
        //     Debug.Log("Jump Polled!");
        // }
    }
}
使用:将 PlayerInputActions资产拖入场景,或使用 PlayerInput组件(见下文)。然后将 PlayerMovement_IS.csPlayerMovement_IS_Polling.cs挂载到角色上。运行游戏,WASD 和手柄都能控制移动,空格键和手柄 A 键会触发跳跃日志。
6. 使用 PlayerInput 组件简化流程
PlayerInput组件可以自动帮你管理 Input Actions 的启用/禁用和事件的派发,让你无需编写 OnEnable/OnDisable和事件注册代码。
  • 创建一个空 GameObject,命名为 Player,将角色模型和 Rigidbody放进去。
  • Player添加 PlayerInput组件。
  • PlayerInputActions资产拖入 Actions槽位。
  • Behavior可以选择 Send Messages(向子物体发送消息)、Broadcast MessagesInvoke Unity Events。选择 Invoke Unity Events
  • 展开 Events -> Gameplay,你可以直接将 PlayerMovement_IS脚本中的方法(如 OnMovePerformed)拖拽到对应的 Move (Action)事件的 performedcanceled槽位中。这让事件与逻辑的绑定在 Inspector 中可视化,非常方便。

六、运行结果与测试步骤

  1. 传统系统测试
    • PlayerMovement_IM.cs挂载到场景中的一个胶囊体上。
    • 运行游戏,使用 WASD 键,观察胶囊体平滑移动。使用方向键效果相同。
  2. 新系统(事件驱动)测试
    • 按照上述步骤配置好 PlayerInputActionsPlayerMovement_IS.cs
    • 运行游戏,使用 WASD 和手柄左摇杆,观察移动效果。按下空格键或手柄 A 键,查看 Console 窗口的日志输出。
  3. 新系统(轮询)测试
    • 使用 PlayerMovement_IS_Polling.cs替换脚本,禁用事件注册部分的代码。
    • 运行游戏,验证逻辑是否与事件驱动版本一致。
  4. 控制方案切换(概念验证)
    • PlayerInputActions资产中,选中 GameplayAction Map,在 Inspector 中点击 Control Schemes​ 旁边的 +号,创建两个方案:Keyboard&MouseGamepad
    • 将对应的绑定拖入各自的方案中。
    • 在运行时,你可以通过代码 inputActions.bindingMask = InputBinding.MaskByGroup("Gamepad");来强制使用手柄方案,或者在 PlayerInput组件中提供一个 UI 来切换。这为玩家自定义按键奠定了基础。

七、部署场景与疑难解答

部署场景

  • 平台通用性:两套系统都支持 Unity 的所有构建平台。
  • 新 Input System 的优势:在需要玩家自定义按键多设备无缝切换(如 PC 用手柄,主机用键盘)或VR的游戏中,新系统是必不可少的。

疑难解答

  1. 问题:添加了新 Input System 包后,游戏无法运行,报 Input System is not enabled错误。
    • 原因:Player Settings 中的 Active Input Handling 没有正确设置。
    • 解决:前往 Edit -> Project Settings -> Player -> Other Settings,将 Active Input Handling​ 设置为 Input System Package (New)。如果还需要兼容旧的 Input Manager 代码,可以设置为 Both,但首次安装后建议重启编辑器。
  2. 问题:新 Input System 的事件不触发。
    • 原因 1:忘记在 OnEnable中调用 actions.Enable(),或在 OnDisable中调用 actions.Disable()
    • 原因 2:事件回调函数签名不正确。必须是 void FunctionName(InputAction.CallbackContext context)
    • 原因 3:没有为 Input Actions Asset 生成 C# 类(如果不使用 PlayerInput组件手动实例化)。
    • 解决:仔细检查生命周期方法和函数签名。
  3. 问题:传统 Input Manager 在新 Input System 启用后失效。
    • 原因:当 Active Input Handling 设置为 Input System Package (New)​ 时,传统的 Input类在大多数平台上会被禁用。
    • 解决:如果需要同时使用,必须将 Active Input Handling 设置为 Both。但请注意,这会增加编译后代码的大小。
  4. 问题:在 IL2CPP 后端下,新 Input System 报 InvalidOperationException: No valid actions found之类的错误。
    • 原因:IL2CPP 的链接器可能将 Input Actions 类或相关的序列化数据裁剪掉了。
    • 解决:在项目中使用 [Preserve]属性标记相关类,或者确保 Input Actions Asset 在构建时被正确引用(例如,通过将它放在 Resources 文件夹中,或由一个场景中的组件引用)。

八、未来展望与技术趋势

  • 全面取代:Unity 的长期目标是让新一代 Input System 完全取代传统的 Input Manager。新项目应毫不犹豫地选择新系统。
  • XR 输入标准化:新 Input System 是 Unity 在 VR/AR 领域的基石,它将持续增加对更复杂 XR 设备(如眼动追踪、面部捕捉)的原生支持。
  • 更强的可定制性与工具链:未来的版本可能会提供更强大的可视化工具,用于创建和管理复杂的输入映射,甚至可能集成到 Unity Live Capture 等管线中,用于动捕数据的实时输入。
  • AI 辅助输入:结合机器学习,未来可能出现能根据玩家习惯自动调整控制方案或提供输入建议的智能系统。

九、总结

特性
传统 Input Manager
新一代 Input System
核心模型
基于预设轴向的轮询
基于动作、绑定、控制的事件/回调模型
配置方式
编辑器静态配置
可视化 Asset + C# 代码
API 风格
轮询式 (Input.GetKey)
事件驱动​ 与 轮询​ (actions.action.performed)
跨平台/设备
基础支持
卓越,原生支持手柄、触控、VR/AR
玩家自定义
极其困难
内置支持,可在运行时修改绑定
复杂交互
需手动编码实现
内置支持​ (Interactions: Hold, Tap, MultiTap)
学习曲线
平缓
较陡峭,但功能强大
未来趋势
已弃用/维护模式
官方主推的未来
核心决策指南
  1. 新项目无条件选择新一代 Input System。其现代化的设计、强大的功能和光明的未来使其成为不二之选。
  2. 维护老项目:如果项目庞大且稳定,继续使用传统 Input Manager 是无可厚非的。但在添加新功能时,可以考虑逐步迁移关键部分。
  3. 拥抱事件驱动:尽可能使用新 Input System 的事件驱动模型,它能写出更清晰、耦合度更低、更易于维护的代码。
  4. 活用 Action Maps 和 Control Schemes:它们是管理复杂游戏输入(如主菜单、游戏内、游戏内菜单)和实现平台无缝切换的秘密武器。
掌握 Unity 的输入系统,就是掌握了与玩家沟通的钥匙。新一代 Input System 不仅是一套更先进的工具,更是一种更符合现代软件开发理念的思维方式。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。