EventSystem
-
事件系统的基本构成
- EventSystem 组件:这是 Unity 事件系统的核心。一个场景中通常有一个 EventSystem 对象,它管理着整个事件处理流程。它包含了一些重要的属性,如 “firstSelectedGameObject”(指定最初选中的游戏对象)和 “sendNavigationEvents”(决定是否发送导航事件)。
- 输入模块(Input Modules):
- StandaloneInputModule:用于处理独立平台(如 PC)的输入事件,包括鼠标和键盘输入。它定义了各种输入操作对应的事件,如鼠标点击、按键按下等如何被处理。
- TouchInputModule:主要用于移动设备等触摸屏幕的输入处理。它能够识别触摸开始、触摸移动和触摸结束等事件,并将这些事件发送给相应的游戏对象进行处理。
- 射线投射器(Raycasters):
- GraphicRaycaster:用于检测 UI 元素的交互。当有输入事件发生时,它会从输入点(如鼠标点击位置)发射射线,检测射线与 UI 元素是否相交。如果相交,则将事件发送给对应的 UI 游戏对象。
- PhysicsRaycaster:用于检测 3D 物体的交互。它会在 3D 场景中发射物理射线,当射线与 3D 物体碰撞时,将事件发送给被碰撞的物体。
-
事件类型及处理接口
- 常见事件类型:
- 鼠标事件:
- PointerClick:当鼠标点击(按下并松开)在一个可交互的对象上时触发。
- PointerDown:鼠标按键在对象上按下时触发。
- PointerUp:鼠标按键在对象上松开时触发。
- PointerEnter:鼠标指针进入一个对象的区域时触发,对于 UI 元素或 3D 物体都适用。
- PointerExit:鼠标指针离开一个对象的区域时触发。
- 触摸事件(针对移动设备):
- TouchDown:手指触摸屏幕时触发,类似于鼠标按下事件。
- TouchUp:手指离开屏幕时触发,类似于鼠标松开事件。
- TouchMove:手指在屏幕上移动时触发。
- 键盘事件:
- KeyDown:键盘上的按键按下时触发,用于处理如角色移动(通过方向键)、功能触发(如回车键确认)等操作。
- KeyUp:键盘上的按键松开时触发。
- 鼠标事件:
- 事件处理接口:
- IPointerClickHandler:实现此接口的脚本需要定义
OnPointerClick
方法,用于处理PointerClick
事件。例如:using UnityEngine; using UnityEngine.EventSystems; public class MyClickHandler : MonoBehaviour, IPointerClickHandler { public void OnPointerClick(PointerEventData eventData) { Debug.Log("对象被点击了"); } }
- IPointerClickHandler:实现此接口的脚本需要定义
- 常见事件类型:
- IPointerDownHandler、IPointerUpHandler、IPointerEnterHandler、IPointerExitHandler:分别用于处理
PointerDown
、PointerUp
、PointerEnter
和PointerExit
事件,方法定义类似OnPointerClick
,只是方法名分别为OnPointerDown
、OnPointerUp
、OnPointerEnter
和OnPointerExit
。 - ITouchDownHandler、ITouchUpHandler、ITouchMoveHandler(针对移动设备):这些接口用于处理触摸事件,脚本需要定义对应的
OnTouchDown
、OnTouchUp
和OnTouchMove
方法来处理相应的触摸事件。 - IKeyDownHandler、IKeyUpHandler(用于键盘事件):通过实现这些接口,在
OnKeyDown
和OnKeyUp
方法中处理键盘按键按下和松开事件。
-
事件系统在 UI 和 3D 物体交互中的应用
- UI 交互:
- 在制作 UI 界面时,如按钮、滑块、文本框等元素,通过实现相应的事件接口来实现交互功能。例如,对于一个按钮,实现
IPointerClickHandler
接口,在OnPointerClick
方法中可以编写打开一个新窗口、提交表单或者执行其他游戏逻辑的代码。 - 利用
GraphicRaycaster
来确保 UI 元素能够正确接收和处理事件。可以通过调整GraphicRaycaster
的属性,如 “Blocking Objects”(指定阻挡射线的物体类型)来优化 UI 元素的事件接收。
- 在制作 UI 界面时,如按钮、滑块、文本框等元素,通过实现相应的事件接口来实现交互功能。例如,对于一个按钮,实现
- 3D 物体交互:
- 对于 3D 游戏中的物体,如道具、角色、建筑等,通过
PhysicsRaycaster
来实现交互。首先,3D 物体需要有碰撞体(Collider)组件,这样PhysicsRaycaster
发射的射线才能与物体产生碰撞并发送事件。 - 例如,在一个角色扮演游戏中,玩家点击一个 3D 道具来拾取它。可以在道具的脚本中实现
IPointerClickHandler
接口,在OnPointerClick
方法中编写拾取道具的逻辑,如将道具添加到玩家背包、更新 UI 显示等。同时,要注意设置好PhysicsRaycaster
的参数,如射线的长度、层级过滤等,以确保正确地检测到 3D 物体的交互。
- 对于 3D 游戏中的物体,如道具、角色、建筑等,通过
- UI 交互:
Unity事件系统中的输入模块
-
StandaloneInputModule(独立平台输入模块)
- 功能概述
- 主要用于处理非触摸设备(如 PC)的输入事件,涵盖了鼠标和键盘输入。它作为一个桥梁,将底层的输入设备信号转换为 Unity 事件系统能够理解和处理的事件类型,使游戏对象能够对玩家的操作做出响应。
- 鼠标事件处理
- 点击事件(PointerClick):当玩家按下并松开鼠标按键时,StandaloneInputModule 会检测到这个操作。它会通过射线投射(通常是 GraphicRaycaster 或 PhysicsRaycaster)来确定鼠标点击的目标对象。如果目标对象有对应的事件处理脚本(如实现了 IPointerClickHandler 接口),则会调用该脚本中的 OnPointerClick 方法来处理点击事件。例如,在一个游戏中,点击一个 UI 按钮来打开一个菜单,就是通过这种方式实现的。
- 按下和松开事件(PointerDown 和 PointerUp):在鼠标按键按下和松开的瞬间,会分别触发这两个事件。这些事件对于一些需要精确控制鼠标按键状态的操作非常有用,比如在射击游戏中,按下鼠标左键开始射击,松开鼠标左键停止射击。游戏对象可以通过实现 IPointerDownHandler 和 IPointerUpHandler 接口来处理这些事件。
- 鼠标进入和离开事件(PointerEnter 和 PointerExit):当鼠标指针进入或离开一个游戏对象的碰撞区域或 UI 元素的范围时,会触发相应的事件。这些事件可以用于实现鼠标悬停效果,如改变 UI 按钮的颜色或显示提示信息。对应的处理接口是 IPointerEnterHandler 和 IPointerExitHandler。
- 键盘事件处理
- 按键按下和松开事件(KeyDown 和 KeyUp):StandaloneInputModule 能够捕获键盘上每个按键的按下和松开动作。对于游戏中的角色控制,如通过方向键(W、A、S、D)移动角色,或者通过回车键确认操作等,都是通过处理这些键盘事件来实现的。游戏对象可以通过实现 IKeyDownHandler 和 IKeyUpHandler 接口来响应键盘事件。例如,在一个平台跳跃游戏中,按下空格键让角色跳跃,就是在脚本中通过处理 KeyDown 事件来实现的。
- 事件发送顺序和优先级
- 当多个游戏对象重叠或者在同一射线投射路径上时,事件发送有一定的顺序和优先级。一般来说,位于上层(更靠近屏幕)的 UI 元素会先接收到事件。对于 3D 物体,如果它们在同一射线投射线上,距离射线起点更近的物体可能会先接收到事件。不过,这种顺序也可以通过设置射线投射器的属性和游戏对象的层级关系等来进行调整。
- 功能概述
-
TouchInputModule(触摸输入模块)
- 功能概述
- 专为移动设备和触摸屏幕设备设计,用于处理触摸相关的输入事件。它能够识别手指在屏幕上的各种操作,如触摸开始、触摸移动和触摸结束,并将这些操作转换为相应的事件,使游戏能够在触摸设备上提供良好的交互体验。
- 触摸事件处理
- 触摸开始事件(TouchDown):当手指首次接触屏幕时触发。类似于鼠标按下事件,这个事件可以用于启动一个操作,比如在一个触摸游戏中,触摸开始可以表示选中一个游戏对象或者开始一个触摸手势。游戏对象可以通过实现 ITouchDownHandler 接口来处理这个事件。
- 触摸移动事件(TouchMove):在手指在屏幕上滑动的过程中,TouchInputModule 会持续检测并发送这个事件。此事件对于实现诸如滑动屏幕来控制游戏视角、拖动物体等操作非常关键。游戏对象通过实现 ITouchMoveHandler 接口来响应这个事件。例如,在一个拼图游戏中,通过触摸移动来拖动拼图块。
- 触摸结束事件(TouchUp):手指离开屏幕时触发。类似于鼠标松开事件,它可以用于结束一个操作,比如释放一个被拖动的物体或者确认一个触摸手势。对应的处理接口是 ITouchUpHandler。
- 多点触摸处理
- TouchInputModule 支持多点触摸操作。它可以同时处理多个手指在屏幕上的操作,并为每个触摸点生成独立的事件。例如,在一些音乐游戏中,玩家可以使用多个手指同时触摸屏幕来弹奏多个音符,或者在缩放地图的操作中,通过两个手指的触摸和移动来实现缩放功能。通过对每个触摸点的事件进行分别处理,可以实现复杂的多点触摸交互。
- 与 StandaloneInputModule 的对比和转换
- 在一些跨平台游戏中,可能需要根据设备类型来切换输入模块。通常,游戏会首先检测设备是触摸设备还是非触摸设备,然后启用相应的输入模块。Unity 提供了一定的机制来方便这种转换。不过,在设计游戏交互时,需要注意两种输入模块在事件处理方式和用户操作习惯上的差异,以确保游戏在不同设备上都能提供一致的、良好的用户体验。
- 功能概述
-
OVRInputModule(虚拟现实和增强现实相关输入模块)
用于 Oculus VR 设备,可处理来自 Oculus Touch 手柄和键盘等的输入,如设置手柄和键盘上操作 UI 的按键,还能结合 OVRGazePointer 实现凝视交互等。
PointerInputModule(通用指针输入模块)
PointerInputModule:提供了 StandaloneInputModule 和 TouchInputModule 的基本功能,同时可以通过代码进行更灵活的访问和定制,可用于需要同时处理多种类型输入或在不同输入模式之间切换的场景
自定义输入模块
开发者可以继承 BaseInputModule 类来创建自定义输入模块,以满足特定的输入设备或交互需求,如自定义手柄输入模块、体感设备输入模块等
使用PointerInputModule实现不同输入类型之间的切换
以下是使用 PointerInputModule 实现不同输入类型之间切换的几种方法:
基于设备类型进行切换
- 使用平台判断语句:在代码中通过
SystemInfo.deviceType
来判断当前运行的设备类型,例如:using UnityEngine; public class InputSwitcher : MonoBehaviour { private PointerInputModule pointerInputModule; void Start() { pointerInputModule = GetComponent<PointerInputModule>(); if (SystemInfo.deviceType == DeviceType.Handheld) { // 启用触摸输入相关设置 pointerInputModule.enabled = true; } else if (SystemInfo.deviceType == DeviceType.Desktop) { // 启用鼠标键盘输入相关设置 pointerInputModule.enabled = true; } } }
- 利用预编译指令:在 Unity 中,可以使用预编译指令
#if
来根据不同的平台进行代码的选择性编译,例如:#if UNITY_ANDROID || UNITY_IOS // 启用触摸输入相关代码 #elif UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX // 启用鼠标键盘输入相关代码 #endif
在运行时根据用户操作或游戏状态切换
- 通过按钮点击切换:在游戏中设置一个切换按钮,当用户点击该按钮时,在脚本中改变 PointerInputModule 的相关设置以切换输入类型,例如:
using UnityEngine; using UnityEngine.EventSystems; public class InputSwitchButton : MonoBehaviour, IPointerClickHandler { private PointerInputModule pointerInputModule; void Start() { pointerInputModule = FindObjectOfType<PointerInputModule>(); } public void OnPointerClick(PointerEventData eventData) { if (pointerInputModule!= null) { // 假设当前是鼠标键盘输入模式,切换到触摸输入模式 if (pointerInputModule is StandaloneInputModule) { pointerInputModule.enabled = false; TouchInputModule touchInputModule = FindObjectOfType<TouchInputModule>(); if (touchInputModule!= null) { touchInputModule.enabled = true; } } // 假设当前是触摸输入模式,切换到鼠标键盘输入模式 else if (pointerInputModule is TouchInputModule) { pointerInputModule.enabled = false; StandaloneInputModule standaloneInputModule = FindObjectOfType<StandaloneInputModule>(); if (standaloneInputModule!= null) { standaloneInputModule.enabled = true; } } } } }
- 根据游戏场景或状态切换:例如在游戏的不同关卡或不同游戏模式下,需要不同的输入方式,可以在相应的场景加载或模式切换时进行输入模块的切换,比如:
using UnityEngine; public class GameModeManager : MonoBehaviour { private PointerInputModule pointerInputModule; void Start() { pointerInputModule = GetComponent<PointerInputModule>(); } void OnLevelLoaded(int level) { if (level == 1) { // 启用触摸输入 pointerInputModule.enabled = true; } else if (level == 2) { // 启用鼠标键盘输入 pointerInputModule.enabled = true; } } void OnGameModeChanged(GameMode mode) { if (mode == GameMode.Exploration) { // 启用触摸输入 pointerInputModule.enabled = true; } else if (mode == GameMode.Combat) { // 启用鼠标键盘输入 pointerInputModule.enabled = true; } } }
自定义输入切换脚本
- 继承
BaseInputModule
类创建自定义的输入模块切换器,在其中根据需要动态地切换不同的输入模块,例如:using UnityEngine; using UnityEngine.EventSystems; public class CustomInputSwitcher : BaseInputModule { private StandaloneInputModule standaloneInputModule; private TouchInputModule touchInputModule; void Start() { standaloneInputModule = FindObjectOfType<StandaloneInputModule>(); touchInputModule = FindObjectOfType<TouchInputModule>(); } public void SwitchToStandaloneInput() { if (standaloneInputModule!= null) { standaloneInputModule.enabled = true; touchInputModule.enabled = false; } } public void SwitchToTouchInput() { if (touchInputModule!= null) { touchInputModule.enabled = true; standaloneInputModule.enabled = false; } } public override void Process() { // 这里可以根据当前启用的输入模块进行相应的处理 if (standaloneInputModule!= null && standaloneInputModule.enabled) { standaloneInputModule.Process(); } else if (touchInputModule!= null && touchInputModule.enabled) { touchInputModule.Process(); } } }