Unity引擎输入系统(Input)底层原理


Unity引擎输入系统(Input)底层原理

Unity的输入系统负责接收和处理来自各种输入设备(键盘、鼠标、手柄、触摸屏等)的用户输入,并将其转换为游戏逻辑可以使用的事件和数据。Unity目前有两个输入系统:

  • 旧输入系统(Legacy Input Manager):基于Input类,使用静态接口访问输入数据。
  • 新输入系统(Input System Package):模块化、事件驱动,支持更多设备和复杂输入映射。

下面重点讲解这两者的底层原理和实现机制。


1. 旧输入系统(Legacy Input Manager)

1.1 输入数据的获取

  • Unity的旧输入系统是基于平台原生API封装的。
  • 在不同平台(Windows、macOS、iOS、Android等)上,Unity通过调用操作系统提供的输入API(如Windows的Win32 API、macOS的Cocoa事件、Android的InputEvent等)来获取输入数据。
  • 这些原生输入事件会被Unity引擎的底层代码捕获并转换成统一格式。

1.2 输入状态的缓存与查询

  • Unity在每一帧开始时,会从操作系统拉取当前所有输入设备的状态(按键是否按下、鼠标位置、触摸点等)。
  • 这些状态被缓存到内部数据结构中。
  • 脚本调用Input.GetKeyInput.GetMouseButton等接口时,实际上是查询这些缓存的状态。
  • 例如,Input.GetKeyDown会检测当前帧按键状态与上一帧的差异,判断是否是“按下”事件。

1.3 输入事件的处理流程

  1. 操作系统事件捕获:底层平台API捕获输入事件。
  2. Unity引擎底层处理:将原生事件转换为统一的输入状态。
  3. 状态缓存:每帧更新输入状态缓存。
  4. 脚本查询:开发者通过Input类接口查询输入状态。
  5. 事件响应:游戏逻辑根据输入状态做出响应。

1.4 优缺点

  • 优点:简单易用,适合大多数基础输入需求。
  • 缺点:功能有限,扩展性差,不支持复杂输入映射和多设备管理。

2. 新输入系统(Input System Package)

Unity从2019年开始推出了新的输入系统,设计目标是更灵活、模块化,支持更多设备和复杂输入需求。

2.1 架构设计

  • 设备抽象层(Device Abstraction Layer):统一管理各种输入设备(键盘、鼠标、手柄、触摸屏、VR控制器等)。
  • 输入事件系统(Event System):基于事件驱动,输入事件会被捕获并分发给监听者。
  • 输入动作(Input Actions):开发者定义的输入映射,支持多种绑定(按键、手柄按钮、触摸手势等)。
  • 输入处理管线(Input Processing Pipeline):支持过滤、转换、合成输入数据。

2.2 设备管理

  • 新输入系统通过底层平台API获取设备列表和状态。
  • 支持热插拔,设备连接或断开时会动态更新。
  • 每个设备都有自己的状态缓存和事件队列。

2.3 输入事件流

  • 设备产生原始输入事件(如按键按下、轴移动)。
  • 事件被封装成InputEvent对象,进入事件队列。
  • 输入系统根据定义的InputAction映射,将原始事件转换为游戏逻辑事件。
  • 事件分发给注册的回调函数或通过轮询接口查询。

2.4 输入动作(Input Actions)

  • 输入动作是对输入的抽象,允许开发者定义“跳跃”、“移动”等动作,而不关心具体按键。
  • 支持多绑定(例如“跳跃”可以绑定空格键和手柄A键)。
  • 支持复合绑定(如方向键组合成移动向量)。
  • 支持动作的启用/禁用,方便管理不同游戏状态下的输入。

2.5 脚本接口

  • 通过InputAction类和InputActionMap管理输入。
  • 支持事件回调(performedstartedcanceled)和轮询。
  • 支持自定义输入处理逻辑。

2.6 优缺点

  • 优点:灵活强大,支持多设备、多平台,易于扩展和维护。
  • 缺点:学习曲线较陡,配置较复杂。

3. 底层实现技术

  • Unity引擎使用C++编写底层输入模块,调用操作系统API。
  • 输入数据通过引擎内部的事件队列和状态缓存传递给C#层。
  • 新输入系统使用C#实现大部分逻辑,底层仍依赖平台API和引擎C++代码。
  • 事件驱动模型减少了轮询开销,提高响应效率。

4. 总结

特点 旧输入系统(Input Manager) 新输入系统(Input System Package)
设计模式 轮询查询 事件驱动 + 轮询
设备支持 基础设备(键盘、鼠标、手柄) 多设备(包括VR、触摸、手势等)
输入映射 静态,基于字符串 动态,基于InputAction和InputActionMap
扩展性
热插拔支持 不完善 完善
学习难度 较高

我们继续深入讲解Unity输入系统的底层原理,重点介绍新输入系统(Input System Package)的更多细节,包括事件处理机制、设备管理、输入动作的内部工作流程,以及与平台底层API的交互。


Unity新输入系统(Input System Package)底层原理深入解析


5. 输入事件处理机制

5.1 事件捕获与队列

  • 原生事件捕获
    Unity引擎底层(C++层)通过调用操作系统的输入API捕获原始输入事件。例如:

    • Windows平台调用Win32的GetRawInputDataWM_INPUT消息。
    • macOS调用Cocoa的NSEvent
    • Android调用InputEvent
    • iOS调用UIEvent
  • 事件封装
    捕获的原生事件被封装成统一的InputEvent结构,包含事件类型(按键、轴、触摸等)、设备ID、时间戳、数值等信息。

  • 事件队列
    这些事件被放入引擎的输入事件队列中,等待后续处理。

5.2 事件分发

  • 事件轮询
    Unity主循环中,输入系统会定期从事件队列中取出事件,进行处理。

  • 设备状态更新
    事件会更新对应设备的状态缓存(按键状态、轴值、触摸点等)。

  • 动作映射转换
    输入系统根据开发者定义的InputActionMap,将原始事件映射到对应的输入动作(如“跳跃”、“移动”)。

  • 事件回调触发
    对应的InputAction会触发事件回调(startedperformedcanceled),通知游戏逻辑。


6. 设备管理机制

6.1 设备枚举与识别

  • Unity新输入系统启动时,会调用平台API枚举当前连接的所有输入设备。
  • 每个设备被分配唯一的设备ID。
  • 设备类型(键盘、鼠标、游戏手柄、触摸屏、VR控制器等)由设备描述符确定。
  • 设备信息包括厂商ID、产品ID、设备名称、支持的控件(按钮、轴、触摸点等)。

6.2 设备热插拔

  • 新输入系统支持设备的动态连接和断开。
  • 当设备插入时,底层捕获事件,通知输入系统添加新设备。
  • 当设备断开时,输入系统清理对应设备状态,触发断开事件。
  • 游戏逻辑可以监听设备连接/断开事件,动态调整输入处理。

6.3 设备状态缓存

  • 每个设备维护自己的状态缓存,存储当前所有控件的状态。
  • 状态缓存用于快速查询和事件生成。
  • 例如,键盘设备缓存所有按键的按下/抬起状态,鼠标设备缓存位置和按钮状态。

7. 输入动作(Input Actions)内部工作流程

7.1 输入动作定义

  • 输入动作由InputAction对象表示,包含动作名称、类型(按钮、值、向量等)、绑定列表。
  • 绑定(Binding)定义了动作与设备控件的映射关系,例如“跳跃”绑定空格键和手柄A键。

7.2 复合绑定和处理

  • 支持复合绑定,如方向键组合成二维向量。
  • 复合绑定内部会监听多个控件的状态,合成一个统一的输入值。

7.3 动作状态机

  • 每个输入动作维护一个状态机,跟踪动作的生命周期:

    • Started:动作开始(如按键按下)。
    • Performed:动作执行(如按键持续按下或值变化)。
    • Canceled:动作取消(如按键抬起)。
  • 状态机根据输入事件更新状态,并触发对应回调。

7.4 输入处理管线

  • 输入事件经过一系列处理器(Processors),如死区处理、缩放、滤波等,转换成最终输入值。
  • 处理器可以自定义,方便实现复杂输入逻辑。

8. 与平台底层API的交互

8.1 跨平台抽象层

  • Unity为不同平台封装了统一的输入接口,屏蔽平台差异。
  • 例如,Windows使用Raw Input,macOS使用IOKit和Cocoa事件,Android使用JNI调用Java层输入事件。

8.2 性能优化

  • 事件驱动减少了轮询开销,只有输入发生时才处理事件。
  • 状态缓存避免重复查询底层API。
  • 支持多线程处理输入事件,减少主线程压力。

9. 典型输入事件流程示例

以按键按下为例:

  1. 用户按下键盘上的空格键。
  2. 操作系统生成按键按下事件,传递给Unity底层。
  3. Unity底层捕获事件,封装成InputEvent,放入事件队列。
  4. 输入系统从队列取出事件,更新键盘设备状态缓存。
  5. 输入系统查找所有绑定了空格键的InputAction,更新动作状态机。
  6. 动作状态机进入startedperformed状态,触发回调。
  7. 游戏逻辑收到回调,执行跳跃动作。

10. 总结

Unity新输入系统通过事件驱动、设备抽象、动作映射等机制,实现了灵活、高效、跨平台的输入处理。底层依赖平台API捕获原生事件,经过统一封装和处理,最终以易用的接口暴露给开发者。


我们继续深入探讨Unity新输入系统(Input System Package)的底层实现细节,重点介绍源码结构、关键类、扩展机制(Processor和Interaction),以及平台输入API的具体调用细节。


Unity新输入系统底层源码结构与关键类解析


11. 源码结构概览

Unity新输入系统的源码主要分布在Unity的InputSystem模块中,核心代码用C#编写,部分底层平台接口用C++实现(封装在引擎内部)。

主要命名空间和模块:

  • UnityEngine.InputSystem:核心API和数据结构。
  • UnityEngine.InputSystem.LowLevel:底层事件和设备接口。
  • UnityEngine.InputSystem.Controls:各种控件(按钮、轴、向量等)定义。
  • UnityEngine.InputSystem.Layouts:设备布局和控件映射。
  • UnityEngine.InputSystem.Processors:输入处理器。
  • UnityEngine.InputSystem.Interactions:输入交互器。
  • UnityEngine.InputSystem.Utilities:工具类和辅助函数。

12. 关键类解析

12.1 InputSystem

  • 入口类,管理设备、事件队列、输入动作。
  • 负责初始化、更新输入状态、分发事件。
  • 维护设备列表和动作映射。

12.2 InputDevice

  • 抽象基类,表示输入设备。
  • 包含设备ID、描述符、控件集合。
  • 具体设备类继承自它,如Keyboard, Mouse, Gamepad

12.3 InputControl

  • 表示设备上的单个控件(按钮、轴、触摸点等)。
  • 负责存储控件状态,提供状态读取接口。
  • 支持复合控件(如二维向量由多个轴组成)。

12.4 InputEvent

  • 低层输入事件结构,封装原生输入数据。
  • 包含事件类型、时间戳、设备ID、控件值等。

12.5 InputAction 和 InputActionMap

  • InputAction表示单个输入动作。
  • InputActionMap管理一组相关的输入动作。
  • 支持启用/禁用、绑定管理、事件回调。

12.6 InputProcessor

  • 抽象类,定义输入数据的处理接口。
  • 处理器链可以对输入值进行变换,如死区处理、缩放、滤波。
  • 例如NormalizeProcessorInvertProcessor

12.7 InputInteraction

  • 抽象类,定义输入动作的交互逻辑。
  • 处理复杂交互,如长按、双击、拖拽。
  • 例如HoldInteractionTapInteraction

13. 扩展机制详解

13.1 输入处理器(Processor)

  • 作用:对输入控件的原始值进行处理,输出最终值。

  • 处理器链可以串联多个处理器,逐个处理输入数据。

  • 典型用途:

    • 过滤噪声(Deadzone)
    • 归一化输入范围
    • 反转轴向
    • 缩放输入灵敏度
  • 自定义处理器示例:

public class SquareProcessor : InputProcessor<float>
{
    public override float Process(float value, InputControl control)
    {
        return value * value;
    }
}
  • 注册自定义处理器:
InputSystem.RegisterProcessor<SquareProcessor>();

13.2 输入交互器(Interaction)

  • 作用:定义输入动作的触发条件和生命周期。
  • 例如,HoldInteraction要求按键按住一定时间才触发动作。
  • 支持组合交互,如双击+长按。
  • 自定义交互器示例:
public class CustomInteraction : IInputInteraction
{
    public void Process(ref InputInteractionContext context)
    {
        // 自定义交互逻辑
    }
}
  • 注册自定义交互器:
InputSystem.RegisterInteraction<CustomInteraction>();

14. 平台输入API调用细节

14.1 Windows平台

  • 使用Win32 Raw Input API捕获键盘、鼠标、手柄事件。
  • 通过消息循环(WM_INPUT)接收输入数据。
  • Unity底层C++代码解析原始数据,封装成InputEvent

14.2 macOS平台

  • 使用Cocoa框架的NSEvent捕获输入。
  • 通过IOKit管理设备连接和状态。
  • 事件传递给Unity引擎,转换为统一格式。

14.3 Android平台

  • 通过JNI调用Java层InputEvent接口。
  • 监听触摸、按键、传感器事件。
  • 事件传递给C++层,再传给C#输入系统。

14.4 iOS平台

  • 使用UIEventUITouch捕获触摸和按键事件。
  • 通过Objective-C桥接传递给Unity引擎。

15. 输入系统更新流程(简化版)

  1. 平台事件捕获:操作系统捕获输入事件。
  2. 事件封装:Unity底层封装成InputEvent
  3. 事件入队:事件放入输入事件队列。
  4. 事件处理InputSystem从队列取事件,更新设备状态。
  5. 动作映射:根据InputActionMap映射事件到动作。
  6. 处理器链:输入值经过处理器链处理。
  7. 交互器处理:动作状态机根据交互器逻辑更新状态。
  8. 事件回调:触发动作事件回调,通知游戏逻辑。

16. 迁移与兼容性

  • Unity允许同时使用旧输入系统和新输入系统。
  • 新输入系统支持自动生成旧系统的输入映射,方便迁移。
  • 推荐新项目使用新输入系统,旧项目逐步迁移。

我们继续深入讲解Unity新输入系统的底层实现,重点介绍具体源码文件和类的实现细节,以及如何自定义复杂输入交互和调试输入系统。


17. 具体源码文件和关键类实现细节


17.1 InputSystem.cs

  • 职责:作为输入系统的核心管理类,负责设备管理、事件处理、动作映射和更新循环。
  • 关键方法
    • Initialize():初始化输入系统,注册设备和动作。
    • Update():每帧调用,处理事件队列,更新设备状态,触发动作事件。
    • AddDevice() / RemoveDevice():动态管理设备。
    • QueueEvent():将原生输入事件封装后入队。
  • 实现细节
    • 使用事件队列存储输入事件,保证事件顺序。
    • 维护设备字典,快速索引设备状态。
    • 支持多线程安全的事件入队。

17.2 InputDevice.cs

  • 职责:抽象输入设备,管理控件集合和设备状态。
  • 关键属性
    • deviceId:唯一设备标识。
    • description:设备描述信息。
    • controls:设备上的控件列表。
  • 关键方法
    • MakeCurrent():设置当前活动设备。
    • TryGetChildControl():获取指定类型的控件。
    • FinishSetup():设备初始化完成后调用,构建控件树。
  • 实现细节
    • 设备控件通过反射和布局系统自动绑定。
    • 支持设备热插拔,动态更新控件状态。

17.3 InputControl.cs

  • 职责:表示设备上的单个控件(按钮、轴、向量等)。
  • 关键属性
    • stateBlock:控件状态的内存布局。
    • value:当前控件值。
  • 关键方法
    • ReadValue():读取控件当前值。
    • WriteValueIntoBuffer():写入控件值到状态缓存。
  • 实现细节
    • 控件状态存储在底层内存块,保证高效访问。
    • 支持复合控件,组合多个控件的值。

17.4 InputAction.cs

  • 职责:表示单个输入动作,管理绑定和状态机。
  • 关键属性
    • bindings:动作绑定列表。
    • phase:动作当前状态(Started、Performed、Canceled)。
  • 关键方法
    • Enable() / Disable():启用或禁用动作。
    • PerformInteractiveRebinding():支持运行时重新绑定。
    • ProcessEvent():处理输入事件,更新动作状态。
  • 实现细节
    • 动作状态机根据输入事件和交互器逻辑更新状态。
    • 支持事件回调和轮询查询。

18. 自定义复杂输入交互示例


18.1 自定义交互器示例:长按+双击组合

using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Interactions;

public class LongPressDoubleClickInteraction : IInputInteraction
{
    private double lastClickTime = 0;
    private bool isLongPress = false;
    private const double doubleClickThreshold = 0.3;
    private const double longPressDuration = 0.5;

    public void Process(ref InputInteractionContext context)
    {
        if (context.ControlIsActuated())
        {
            if (!isLongPress)
            {
                if (context.time - lastClickTime < doubleClickThreshold)
                {
                    // 双击检测
                    context.Performed();
                    lastClickTime = 0;
                }
                else
                {
                    // 开始计时长按
                    isLongPress = true;
                    context.Started();
                }
            }
            else if (context.time - context.startTime >= longPressDuration)
            {
                // 长按完成
                context.Performed();
            }
        }
        else
        {
            if (isLongPress)
            {
                context.Canceled();
                isLongPress = false;
                lastClickTime = context.time;
            }
            else
            {
                context.Canceled();
            }
        }
    }
}
  • 注册交互器
InputSystem.RegisterInteraction<LongPressDoubleClickInteraction>();
  • 绑定示例
var action = new InputAction("CustomAction", binding: "/leftButton", interactions: "LongPressDoubleClick");
action.Enable();
action.performed += ctx => Debug.Log("Long press or double click detected");

19. 输入系统调试与诊断


19.1 使用Input Debugger

  • Unity Editor内置的Input Debugger窗口(菜单:Window > Analysis > Input Debugger)。
  • 可以查看当前连接的设备、控件状态、输入事件流。
  • 支持实时监控输入事件,帮助定位输入问题。

19.2 日志与事件追踪

  • 在代码中添加事件回调,打印输入事件和动作状态。
  • 使用InputSystem.onEvent订阅所有输入事件,进行日志记录。
InputSystem.onEvent += (inputEvent, device) =>
{
    Debug.Log($"Event: {inputEvent}, Device: {device.displayName}");
};

19.3 性能分析

  • 使用Unity Profiler分析输入系统的CPU占用。
  • 优化输入处理器链,避免复杂处理器过多。
  • 减少不必要的事件回调订阅。

20. 总结

  • Unity新输入系统源码结构清晰,核心类职责明确。
  • 通过自定义Processor和Interaction,可以实现复杂输入逻辑。
  • 平台底层API封装保证跨平台一致性。
  • 利用Input Debugger和日志工具,可以高效调试输入问题。

你可能感兴趣的:(游戏引擎)