Unity Term Book
脚本与生命周期

Input System

Unity 的现代 Input System 包提供灵活重映射、多设备支持与事件驱动 Action,全面取代旧式 Input.GetKey() 轮询 API。适合手柄、触控与键鼠并存的跨平台项目,帮助程序用 Input Actions 资产统一绑定输入,并更易实现本地化按键提示与无障碍输入配置方案。

想象一下...

旧 Input System 像员工每 0.016 秒打电话问“有顾客吗?”(轮询)。新 Input System 装了门铃(事件)——只有有人按铃你才听见。Action Maps 像调度板——同一个“Confirm”在菜单里是 Navigate Accept,在玩法里是 Jump。不需要 `if (context == menu)` 意大利面代码。

概念详解

Input Actions Asset 是一个 .inputactions 文件,保存所有绑定。它组织为 Action Maps(Gameplay、UI、Vehicle…)→ 每个 Map 有 Actions(Move、Jump、Fire…)→ 每个 Action 有 Bindings(按键、摇杆、手柄按钮…)。

事件驱动:订阅 action.startedaction.performedaction.canceled。用 context.ReadValue<Vector2>() 读取值。不必在 Update 中轮询。

Player Input 组件:挂到 GameObject 上可自动接线事件。 支持 Messages、C# Events 或 Unity Events。

运行时重映射: action.AddBinding()InputActionRebindingExtensions.PerformInteractiveRebinding() 让玩家能在 Settings 菜单中改键位。

示意图:Input System 架构

⌨️ Keyboard
🎮 Gamepad
📱 Touch
🖱 Mouse
↓ Device Input

Input Actions Asset (.inputactions)

Action Map: GameplayAction Map: UIAction Map: Vehicle
↓ Binding resolution

Actions: Move, Jump, Fire, Interact...

started / performed / canceled events

↓ C# Events

PlayerController

OnMove(ctx)

UINavigator

OnNavigate(ctx)

VehicleDriver

OnSteer(ctx)

动手步骤

1

安装 Input System 包

Window → Package Manager → Input System → Install。按提示重启。

2

创建 Input Actions Asset

Assets → Create → Input Actions。添加 Action Maps、Actions,以及每个键/按钮的 Bindings。

3

生成 C# 类

选中 .inputactions 文件 → Inspector → 勾选 "Generate C# Class" → Apply → Unity 生成包装类。

4

订阅 / 取消订阅事件

OnEnable:_actions.Gameplay.Jump.performed += OnJump; OnDisable:用同样方式取消订阅。

交互模拟器

按键(或下方按钮),观察事件到达当前活动 Action Map 的正确监听者。

Active Map:

Virtual Input

Event Log

按键查看事件...

Map: Gameplay|Events fired: 0

代码示例

基础

使用生成的 C# 类并订阅事件。

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
  private GameControls _actions; // Generated C# class
  private Rigidbody    _rb;
  private Vector2      _moveInput;

  void Awake()
  {
      _rb      = GetComponent<Rigidbody>();
      _actions = new GameControls();
  }

  void OnEnable()
  {
      _actions.Gameplay.Enable();
      _actions.Gameplay.Move.performed  += OnMove;
      _actions.Gameplay.Move.canceled   += OnMove;
      _actions.Gameplay.Jump.performed  += OnJump;
  }

  void OnDisable()
  {
      _actions.Gameplay.Move.performed  -= OnMove;
      _actions.Gameplay.Move.canceled   -= OnMove;
      _actions.Gameplay.Jump.performed  -= OnJump;
      _actions.Gameplay.Disable();
  }

  void OnMove(InputAction.CallbackContext ctx)
      => _moveInput = ctx.ReadValue<Vector2>();

  void OnJump(InputAction.CallbackContext ctx)
      => _rb.AddForce(Vector3.up * 5f, ForceMode.Impulse);

  void FixedUpdate()
      => _rb.velocity = new Vector3(_moveInput.x * 5f, _rb.velocity.y, _moveInput.y * 5f);
}

代码示例

进阶

交互式重绑定——让玩家在运行时改键位。

using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;

public class KeyRebinder : MonoBehaviour
{
  [SerializeField] private InputActionReference jumpAction;
  [SerializeField] private TextMeshProUGUI bindingLabel;
  private InputActionRebindingExtensions.RebindingOperation _rebindOp;

  public void StartRebind()
  {
      jumpAction.action.Disable();
      bindingLabel.text = "Press any key...";

      _rebindOp = jumpAction.action
          .PerformInteractiveRebinding()
          .WithControlsExcluding("Mouse")
          .OnMatchWaitForAnother(0.1f)
          .OnComplete(op => {
              op.Dispose();
              jumpAction.action.Enable();
              bindingLabel.text = InputControlPath.ToHumanReadableString(
                  jumpAction.action.bindings[0].effectivePath);
              // Persist binding overrides
              PlayerPrefs.SetString("JumpBinding", jumpAction.action.SaveBindingOverridesAsJson());
          })
          .Start();
  }
}

📌 快速记忆

  • 按上下文 Enable/Disable ActionMaps(Gameplay ↔ UI)
  • 在 OnEnable 订阅,在 OnDisable 取消订阅
  • performed = 按下;canceled = 松开
  • 生成 C# 类 → 更清晰的 API + IntelliSense

⚠️ 常见错误

  • ❌ 忘记对 Action Map 调用 Enable()

    即使订阅了 Actions 也不会触发——静默失败

    ✅ 在 OnEnable 中调用 _actions.Gameplay.Enable()

  • ❌ 在 OnDisable 中忘记 Unsubscribe

    内存泄漏——事件会调用到已销毁的对象

    ✅ 始终用 OnEnable/OnDisable 成对 Subscribe/Unsubscribe