30 KiB
InputModule 接入说明
设计原则
InputModule 是可选功能模块。
因此依赖方向必须始终保持为:
InputModule可以依赖当前基座项目约定(UGF /GameEntry/ Procedure 接入方式)- 基座项目默认代码不能反向依赖
InputModule
也就是说:
- 本仓库中的基座代码默认不直接接入输入模块
- 当某个项目决定启用该模块时,再按下面步骤显式接入
编译归属说明
当前模块采用的是:
Base独立程序集Runtime通过 asmref 并入SepCore.Runtime
也就是说:
Assets/Plugins/InputModule/Base/中的代码仍然是独立模块基础层Assets/Plugins/InputModule/Runtime/中的代码虽然命名空间仍是SepCore.InputModule.Runtime,但编译归属属于SepCore.Runtime
这样做的目的,是让 InputModuleComponent 能与基座里的其他 Runtime Component 保持平级协作,避免为了跨程序集调用再额外抽一层接口。
因此这里的“可选”含义是:
- 接入流程是可选的
- 导入模块后是否在项目里启用它是可选的
- 而不是要求 Runtime 代码必须物理编译成一个独立运行时程序集
目录说明
Assets/Plugins/InputModule/Base/- 独立基础层
- 提供
InputContextId、InputActionId、InputCommand等基础定义
Assets/Plugins/InputModule/Runtime/- 运行时代码
- 通过
SepCore.Runtime.InputModule.asmref并入SepCore.Runtime
Assets/Tests/PlayMode/InputModule/- 当前模块的 PlayMode 测试
推荐接入流程
1. 导入模块
将 Assets/Plugins/InputModule 导入到目标项目。
导入后请确认:
Assets/Plugins/InputModule/Base/InputModule.Base.asmdefAssets/Plugins/InputModule/Runtime/SepCore.Runtime.InputModule.asmref
都已一并导入。
其中:
InputModule.Base.asmdef提供基础定义程序集SepCore.Runtime.InputModule.asmref负责把 Runtime 代码并入SepCore.Runtime
2. 在 GameEntry.Custom.cs 中增加模块访问入口
文件:
Assets/GameMain/Scripts/Runtime/Base/GameEntry.Custom.cs
接入示例:
using SepCore.InputModule.Runtime;
using SepCore.CustomComponent;
using UnityEngine;
public partial class GameEntry : MonoBehaviour
{
public static BuiltinDataComponent BuiltinData { get; private set; }
public static InputModuleComponent InputModule { get; private set; }
private static void InitCustomComponents()
{
BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent<BuiltinDataComponent>();
InputModule = UnityGameFramework.Runtime.GameEntry.GetComponent<InputModuleComponent>();
}
}
说明:
InputModule访问入口是接入方项目自己加的- 这一步是“启用模块”的显式行为
- 不启用模块的项目,不需要添加这段代码
- 这里虽然
InputModuleComponent的命名空间是SepCore.InputModule.Runtime,但它实际已经通过 asmref 编译进SepCore.Runtime
3. 在 Launcher 场景中挂载 InputModuleComponent
推荐做法:
- 在
Launcher场景中新建一个 GameObject - 挂载
SepCore.InputModule.Runtime.InputModuleComponent
建议命名:
InputModule
你也可以把它挂在已有的系统节点上,只要生命周期符合项目约定即可。
推荐理解方式:
InputModuleComponent是插件提供的 Runtime Component- 但它不是“高一层服务”,而是与
BuiltinDataComponent、UIComponent、SoundComponent等在运行时层面平级协作
4. 在游戏启动 Procedure 中显式初始化
推荐在某个启动流程里调用:
GameEntry.InputModule.OnInit();
例如:
protected override void OnEnter(IFsm<IProcedureManager> procedureOwner)
{
base.OnEnter(procedureOwner);
GameEntry.InputModule.OnInit();
}
说明:
OnInit()是模块正式开始工作的入口- 这一步会初始化 action asset、缓存 action map、订阅输入回调、按配置加载重绑数据
5. 在具体业务 Procedure / UI 中切换输入上下文
示例:
GameEntry.InputModule.SetContext(InputContextId.UI);
GameEntry.InputModule.SetContext(InputContextId.GameplayExplore);
GameEntry.InputModule.PushContext(InputContextId.Dialog);
GameEntry.InputModule.PopContext();
GameEntry.InputModule.ClearContexts();
最小接入示例
以下是从零到跑通 InputModule 的完整步骤,可直接复制使用。
第一步:GameEntry.Custom.cs
using SepCore.InputModule.Runtime;
using SepCore.CustomComponent;
using UnityEngine;
public partial class GameEntry : MonoBehaviour
{
public static BuiltinDataComponent BuiltinData { get; private set; }
public static InputModuleComponent InputModule { get; private set; }
private static void InitCustomComponents()
{
BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent<BuiltinDataComponent>();
InputModule = UnityGameFramework.Runtime.GameEntry.GetComponent<InputModuleComponent>();
}
}
第二步:启动 Procedure 中调用 OnInit()
protected override void OnEnter(ProcedureOwner procedureOwner)
{
base.OnEnter(procedureOwner);
// ... 其他初始化 ...
GameEntry.InputModule.OnInit();
}
第三步:Launcher 场景挂载
在 Game Framework > Customs 节点下新建 GameObject InputModule,挂载 InputModuleComponent。Inspector 中 _inputActionsAsset 留空即使用默认生成的 action map。
第四步:业务中切换上下文
// 进入游戏玩法
GameEntry.InputModule.SetGameplayExploreContext();
// 打开菜单
GameEntry.InputModule.SetUIContext();
// 关闭菜单,恢复玩法
GameEntry.InputModule.SetGameplayExploreContext();
// 打开对话(从 GameplayExplore 压栈)
GameEntry.InputModule.PushContext(InputContextId.Dialog);
// 关闭对话(弹栈恢复 GameplayExplore)
GameEntry.InputModule.PopContext();
Launcher 场景挂载检查清单
- 在
Game Framework > Customs节点下新建 GameObject,命名InputModule - 挂载
InputModuleComponent(SepCore.InputModule.Runtime.InputModuleComponent) _inputActionsAsset:如果不使用默认 action,拖入自定义InputActionAsset;否则留空(自动使用代码生成的默认 action)_startupContext:推荐设为GameplayExplore或None_loadBindingOverridesOnInit:一般保持true_saveBindingOverridesOnDestroy:按需设置- 检查 EventSystem:
InputSystemUIInputModule保持启用,作为当前阶段 UI 主路径
推荐 GameObject 命名规范
- 推荐命名:
InputModule - 推荐挂载位置:
Game Framework > Customs节点下(与Builtin Data平级) - 不要挂在
Builtin预制体内部(那是框架自带的,不应修改) InputModuleComponent标记了[DisallowMultipleComponent],同一 GameObject 上只能挂一个
与 InputSystemUIInputModule 的职责划分
当前推荐策略是:UI 原始输入继续交给 InputSystemUIInputModule / EventSystem,InputModule 只负责 gameplay 语义输入与上下文切换。
分工:
InputSystemUIInputModule:驱动 Unity EventSystem,处理 UI 交互(按钮点击、拖拽、导航等)StandaloneInputModule:旧版输入模块兜底(是否保留按项目兼容需求决定)InputModuleComponent:处理 gameplay / 业务语义输入(通过CommandTriggered事件和RegisterListener),不直接驱动 EventSystem
推荐策略:
- 移动端 UI: 普通按钮点击、拖拽、滚动等继续走
InputSystemUIInputModule + EventSystem - PC UI: 鼠标指针点击继续走
InputSystemUIInputModule + EventSystem - 主机 UI: 当前阶段也优先沿用 EventSystem 导航能力,而不是为了统一入口额外接管 UI 输入
- Gameplay 输入:
Pause / Move / Interact / Sprint等语义继续由InputModule管理
换句话说:
- UI 事件分发 不作为当前阶段 InputModule 的统一目标
- Gameplay 语义输入统一 才是当前阶段 InputModule 的主线目标
- UI 打开/关闭 对 InputModule 的主要影响是“切换 gameplay context”,而不是“替代 EventSystem”
上下文切换示例
场景 1: 游戏探索中打开菜单
// 打开菜单
GameEntry.InputModule.SetContext(InputContextId.UI);
// 关闭菜单,恢复探索
GameEntry.InputModule.SetGameplayExploreContext();
场景 2: 游戏探索中触发对话
// 对话打开(GameplayExplore 被压栈,只有栈顶 Dialog 生效,GameplayExplore 暂停)
GameEntry.InputModule.PushContext(InputContextId.Dialog);
// 监听对话输入
GameEntry.InputModule.RegisterListener(InputActionId.Confirm, OnDialogConfirm);
GameEntry.InputModule.RegisterListener(InputActionId.Cancel, OnDialogCancel);
// 对话关闭(弹栈,恢复 GameplayExplore)
GameEntry.InputModule.UnregisterListener(InputActionId.Confirm, OnDialogConfirm);
GameEntry.InputModule.UnregisterListener(InputActionId.Cancel, OnDialogCancel);
GameEntry.InputModule.PopContext();
场景 3: UI 中弹出确认对话框
// 弹出确认框(替换当前 UI 上下文)
GameEntry.InputModule.SetContext(InputContextId.Dialog);
// 确认框关闭后恢复 UI
GameEntry.InputModule.SetUIContext();
上下文栈语义
栈的作用是记住恢复历史,而非同时启用所有上下文。ApplyContextState() 只启用 Global + 栈顶上下文:
SetContext(X):清空栈,压入 X → 只有 Global + X 生效PushContext(X):压入 X → 只有 Global + X 生效,之前的上下文被暂停PopContext():弹出栈顶 → 恢复到前一个上下文ClearContexts():清空栈 → 只有 Global 生效
这意味着 PushContext(Dialog) 可以安全地用在任何上下文之上,不需要担心绑定重叠。
Dialog 上下文说明
- Dialog action map 提供
Navigate/Confirm/Cancel三个 action,绑定与 UI map 一致 - 从任何上下文切换到 Dialog 都可用
PushContext(Dialog),栈顶语义保证只有 Dialog 生效 - Dialog 关闭后,用
PopContext()恢复之前的上下文,或用SetContext()指定目标上下文
当前 Phase 1 提供的能力
- 默认
InputActionAsset代码生成 Global / UI / GameplayExplore / Dialog四组 action map- 设备识别:
KeyboardMouseGamepadTouch
- 输入上下文栈
- 命令分发
- 绑定覆盖保存 / 加载骨架
Phase 2:UI 与 Gameplay 协作能力
设计约定
上下文切换采用混合方案:
| 层级 | 职责 | 方式 |
|---|---|---|
| Procedure | 大场景级切换(Menu ↔ Main) | SetContext() 清栈重置 |
| UIForm | 覆盖层管理(Dialog 弹出/关闭) | PushContext() / PopContext() 压栈弹栈 |
UI 阻断 Gameplay 输入:栈语义天然支持。ApplyContextState() 只启用 Global + 栈顶上下文,当 Dialog 压栈后 GameplayExplore 自动禁用。
Procedure 层接入模板
在启动 Procedure 中初始化 InputModule,在业务 Procedure 中设置上下文。
ProcedurePreload(或你选择的启动 Procedure):
protected override void OnEnter(ProcedureOwner procedureOwner)
{
base.OnEnter(procedureOwner);
// ... 其他初始化 ...
GameEntry.InputModule?.OnInit();
}
ProcedureMenu(进入菜单):
protected override void OnEnter(ProcedureOwner procedureOwner)
{
base.OnEnter(procedureOwner);
GameEntry.InputModule?.SetContext(InputContextId.UI);
// ...
}
ProcedureMain(进入游戏):
protected override void OnEnter(ProcedureOwner procedureOwner)
{
base.OnEnter(procedureOwner);
GameEntry.InputModule?.SetContext(InputContextId.GameplayExplore);
// ...
}
UIForm 层接入模板(InputAwareGuiForm)
InputAwareGuiForm 是一个可选的 UIForm 基类,自动管理输入上下文的 Push/Pop。将以下代码复制到你的项目中(例如 Assets/GameMain/Scripts/UI/InputAwareGuiForm.cs):
using SepCore.InputModule;
namespace SepCore.UI
{
public abstract class InputAwareGuiForm : UGuiForm
{
protected virtual InputContextId InputContext => InputContextId.None;
#if UNITY_2017_3_OR_NEWER
protected override void OnOpen(object userData)
#else
protected internal override void OnOpen(object userData)
#endif
{
base.OnOpen(userData);
if (InputContext != InputContextId.None)
{
GameEntry.InputModule?.PushContext(InputContext);
}
}
#if UNITY_2017_3_OR_NEWER
protected override void OnClose(bool isShutdown, object userData)
#else
protected internal override void OnClose(bool isShutdown, object userData)
#endif
{
if (!isShutdown && InputContext != InputContextId.None)
{
GameEntry.InputModule?.PopContext();
}
base.OnClose(isShutdown, userData);
}
}
}
使用方式: 让需要输入上下文管理的 UIForm 继承 InputAwareGuiForm 替代 UGuiForm,覆写 InputContext 属性。
DialogForm 示例:
public class DialogForm : InputAwareGuiForm
{
protected override InputContextId InputContext => InputContextId.Dialog;
// ... 其余不变
}
说明:
InputContext返回None时不做任何上下文操作(默认行为,等同于直接继承UGuiForm)isShutdown时跳过 Pop,避免游戏关闭时栈状态不一致- 所有调用使用
?.运算符,InputModule 未接入时行为不变
业务接入约束
1. 不要直接读取具体设备
业务层代码应该消费 InputCommand 提供的语义信息,而不是直接查询当前设备类型。
推荐:
GameEntry.InputModule.RegisterListener(InputActionId.Move, OnMove);
private void OnMove(InputCommand cmd)
{
// 消费 Vector2 值,不关心来自键盘还是手柄
Vector2 direction = cmd.Vector2Value;
}
避免:
// 不推荐:业务层直接判断设备类型
if (GameEntry.InputModule.CurrentDeviceKind == InputDeviceKind.Gamepad)
{
// 设备相关的差异化逻辑应该放在 presentation 层或专门的适配层
}
原因:
- 设备类型是运行时动态切换的(键鼠 ↔ 手柄热插拔)
- 业务逻辑应该与输入设备解耦,只关心“玩家发出了什么语义指令”
- 如果确实需要设备提示(如 UI 按钮图标切换),应在 UI 层监听
DeviceKindChanged事件,而不是在 gameplay 业务中判断
2. UI 原始输入继续走 EventSystem
InputModule 不接管 UI 原始输入(点击、拖拽、滚动、导航)。UI 交互继续由 Unity EventSystem 处理。
分工:
- UI 点击 / 拖拽 / 滚动 →
EventSystem/InputSystemUIInputModule - Gameplay 语义输入(Move、Interact、Sprint) →
InputModuleComponent - UI 打开 / 关闭 → 只负责切换 gameplay context(如 Push/Pop Dialog),不负责转发 UI 事件
原因:
- EventSystem 已经成熟处理 UI 交互,不需要重复实现
- PC 鼠标指针、Mobile 触摸、主机导航键各自有不同的 UI 处理路径
- 强行统一会引入双重分发,增加维护复杂度
3. UI 打开 / 关闭只切换 context,不接管事件分发
UIForm / Procedure 通过 SetContext / PushContext / PopContext 管理 gameplay 输入的启用与暂停,但不应该尝试通过 InputModule 分发 UI 事件。
正确示例:
// DialogForm 打开时暂停 gameplay 输入
GameEntry.InputModule.PushContext(InputContextId.Dialog);
// DialogForm 中的按钮点击仍然走 UnityEvent / EventSystem
_confirmButton.onClick.AddListener(OnConfirm);
错误示例:
// 不要这样做:试图用 InputModule 替代按钮点击
GameEntry.InputModule.RegisterListener(InputActionId.Confirm, _ =>
{
// 不要在 UI 内部用 Confirm action 模拟按钮点击
// 这会导致 EventSystem 和 InputModule 双重响应
});
平台输入职责划分
PC / 主机
- Gameplay 输入:优先走
InputModule - UI:优先走
EventSystem / InputSystemUIInputModule - 若未来确实需要主机平台专门的 UI 统一入口,再单独设计和验证
移动端
- 普通 UI 点击 / 拖拽 / 滚动:直接走 Unity EventSystem
- 虚拟摇杆 / 虚拟按钮:通过
VirtualJoystickBridge/VirtualButtonBridge适配进 InputModule - 不要求把每一次触屏 UI 操作都抽象成
Confirm / Cancel / Navigate
当前 Phase 1 不负责的内容
- 基座默认自动接入
- 自动修改
GameEntry/ Procedure - 交互式重绑界面
- 具体业务语义绑定(例如战斗输入、菜单输入、剧情输入)
这些都应该在后续阶段或接入方项目里完成。
为什么不能用 InputModuleEntry
像 InputModuleEntry 这种插件内部静态入口,看起来方便,但不符合这里的目标:
- 它绕过了项目自己的
GameEntry接入约定 - 它弱化了“这是一个可选模块,需要显式接入”的边界
- 它会让模块拥有一套平行于基座的入口体系,后续维护容易混乱
所以这里统一采用:
- 模块提供
InputModuleComponent - 项目自己决定是否在
GameEntry中暴露InputModule - 项目自己决定在哪个 Procedure 里调用
OnInit() - 模块 Runtime 代码通过 asmref 并入
SepCore.Runtime,与基座 Component 平级协作
这才符合”可选模块”的接入方式。
P4:设备适配体验
按钮提示映射表(Prompt Map)
InputModuleComponent 提供了一个可选的 prompt map 系统,用于获取”当前设备下某 action 的按键提示”。
核心类型:
InputPrompt(Base 层 readonly struct):承载提示数据TextLabel:文本标签(如”A”,”Enter”,”E”)SpriteName:可选的 sprite 资源键(项目自定义,模块不管理 sprite 资源)HasSprite/IsValid:便捷检查属性
IInputPromptMap(Base 层接口):项目可自定义实现TryGetPrompt(InputActionId, InputDeviceKind, out InputPrompt):查找提示
默认映射表(Xbox 惯例):
| InputActionId | KeyboardMouse | Gamepad | Touch |
|---|---|---|---|
| Pause | Esc |
Menu |
Pause |
| Navigate | WASD |
L Stick |
Swipe |
| Confirm | Enter |
A |
Tap |
| Cancel | Esc |
B |
Back |
| Move | WASD |
L Stick |
Joystick |
| Sprint | L Shift |
L3 |
(无) |
| Interact | E |
A |
Tap |
使用方式:
// 便捷方法:自动使用当前设备类型
if (GameEntry.InputModule.TryGetPrompt(InputActionId.Interact, out InputPrompt prompt))
{
_interactLabel.text = prompt.TextLabel;
}
// 直接查询映射表:指定设备类型
IInputPromptMap map = GameEntry.InputModule.PromptMap;
map.TryGetPrompt(InputActionId.Confirm, InputDeviceKind.Gamepad, out InputPrompt p);
// 替换为自定义映射(如 PS 命名)
GameEntry.InputModule.PromptMap = new MyPlayStationPromptMap();
自定义 PromptMap 示例:
public sealed class PlayStationPromptMap : IInputPromptMap
{
public bool TryGetPrompt(InputActionId actionId, InputDeviceKind deviceKind, out InputPrompt prompt)
{
if (deviceKind == InputDeviceKind.Gamepad)
{
switch (actionId)
{
case InputActionId.Confirm: prompt = new InputPrompt(“Cross”); return true;
case InputActionId.Cancel: prompt = new InputPrompt(“Circle”); return true;
// ...
}
}
// 回退到默认
prompt = InputPrompt.None;
return false;
}
}
提示刷新协议
触发器: DeviceKindChanged 事件。无需单独的 PromptsChanged 事件。
消费模式:
private Action<InputDeviceKind> _onDeviceChanged;
private void OnEnable()
{
_onDeviceChanged = _ => RefreshPrompts();
GameEntry.InputModule.DeviceKindChanged += _onDeviceChanged;
RefreshPrompts(); // 初始状态
}
private void OnDisable()
{
GameEntry.InputModule.DeviceKindChanged -= _onDeviceChanged;
_onDeviceChanged = null;
}
private void RefreshPrompts()
{
if (GameEntry.InputModule.TryGetPrompt(InputActionId.Interact, out InputPrompt p))
_interactLabel.text = p.TextLabel;
if (GameEntry.InputModule.TryGetPrompt(InputActionId.Cancel, out InputPrompt c))
_cancelLabel.text = c.TextLabel;
}
为什么不需要单独的 PromptsChanged 事件?
DeviceKindChanged已经捕获了唯一会改变提示的场景- 如果未来需要”重绑后刷新提示”(不切换设备),可以届时再加新事件
UI 展示约定
KeyboardMouse:
- 显示文本标签(”E”, “Enter”, “Esc”)
- 推荐等宽字体或按键帽风格
- 可选:显示 key-cap sprite(如果
InputPrompt.HasSprite为 true)
Gamepad:
- 显示按钮名(”A”, “B”, “L Stick”)
- 默认使用 Xbox 惯例,PS 项目替换自定义映射
- 可选:显示按钮 sprite
Touch:
- 显示手势描述(”Tap”, “Swipe”)
- 主要用于教程/引导
- Sprint 无默认 touch 提示(虚拟按钮为 P5 范围)
Unknown:
- 隐藏提示元素,或显示通用兜底”Press any button”
- Unknown 状态仅在启动时、首次输入前出现
通用规则:
- 不要硬编码提示字符串,始终查询 PromptMap
TryGetPrompt返回 false 时隐藏或显示兜底
灵敏度/死区配置
评估结论:暂不实现。
理由:
- InputSystem 内置
StickDeadzone(默认 0.125)和AxisDeadzone处理器已自动应用 - 项目中无任何灵敏度/死区相关代码,属于推测性需求
- 当前范围为 PC + 主机,键盘/鼠标无灵敏度概念,手柄使用 InputSystem 默认值已足够
- 移动端(虚拟摇杆死区)为 P5 范围
未来如需实现:Base 层加 InputSensitivityConfig 数据结构,Runtime 层在 EnsureInitialized() 中注入自定义 Processor。
可选:TMP Sprite Asset(输入提示图标)
Assets/Plugins/InputModule/Input_Prompts/ 包含一个预制的 TMP Sprite Asset,可用于在 TextMeshPro 中显示按键图标。
文件说明:
InputPrompt.asset— TMP Sprite Asset,包含键盘、手柄、鼠标等按键图标Input_Prompt.png— 对应的 Sprite 图集(512x512,16x16 单元格)
完整 Sprite 名称对照表:
| Sprite Name | 说明 | Sprite Name | 说明 |
|---|---|---|---|
gamepad_button |
通用手柄按钮(占位) | gamepad_a |
A 键 |
gamepad_b |
B 键 | gamepad_x |
X 键 |
gamepad_y |
Y 键 | gamepad_map |
Map / View 键 |
gamepad_menu |
Menu / Start 键 | gamepad_control |
方向键(十字键) |
gamepad_up_control |
方向键上 | gamepad_right_control |
方向键右 |
gamepad_down_control |
方向键下 | gamepad_left_control |
方向键左 |
gamepad_horizon_control |
方向键水平(占位) | gamepad_vertical_control |
方向键垂直(占位) |
gamepad_full_control |
方向键全方向(占位) | gamepad_ls |
左摇杆 |
gamepad_up_ls |
左摇杆上推 | gamepad_right_ls |
左摇杆右推 |
gamepad_down_ls |
左摇杆下推 | gamepad_left_ls |
左摇杆左推 |
gamepad_horizon_ls |
左摇杆水平(占位) | gamepad_vertical_ls |
左摇杆垂直(占位) |
gamepad_full_ls |
左摇杆全方向(占位) | gamepad_press_ls |
左摇杆按下(L3) |
gamepad_rs |
右摇杆 | gamepad_up_rs |
右摇杆上推 |
gamepad_right_rs |
右摇杆右推 | gamepad_down_rs |
右摇杆下推 |
gamepad_left_rs |
右摇杆左推 | gamepad_horizon_rs |
右摇杆水平(占位) |
gamepad_vertical_rs |
右摇杆垂直(占位) | gamepad_full_rs |
右摇杆全方向(占位) |
gamepad_press_rs |
右摇杆按下(R3) | gamepad_left_trigger |
左扳机(LT) |
gamepad_right_trigger |
右扳机(RT) | gamepad_left_bumper |
左肩键(LB) |
gamepad_right_bumper |
右肩键(RB) | ||
keyboard_esc |
Esc 键 | keyboard_f1 ~ keyboard_f12 |
F1 ~ F12 |
keyboard_1 ~ keyboard_0 |
1 ~ 0 | keyboard_minus |
减号 / 连字符 |
keyboard_equal |
等号 | keyboard_backspace |
退格键 |
keyboard_q ~ keyboard_p |
Q ~ P | keyboard_left_bracket |
左方括号 [ |
keyboard_right_bracket |
右方括号 ] |
keyboard_backslash |
反斜杠 \ |
keyboard_a ~ keyboard_l |
A ~ L | keyboard_single_quote |
单引号 ' |
keyboard_semicolon |
分号 ; |
keyboard_enter |
回车键 |
keyboard_z ~ keyboard_m |
Z ~ M | keyboard_slash |
斜杠 / |
keyboard_up_arrow |
上箭头 | keyboard_right_arrow |
右箭头 |
keyboard_down_arrow |
下箭头 | keyboard_left_arrow |
左箭头 |
keyboard_alt |
Alt 键 | keyboard_tab |
Tab 键 |
keyboard_delete |
Delete 键 | keyboard_period |
句号 . |
keyboard_control |
Ctrl 键 | keyboard_capslock |
Caps Lock |
keyboard_comma |
逗号 , |
keyboard_space |
空格键 |
keyboard_shift |
Shift 键 | ||
mouse |
鼠标指针 | mouse_left |
鼠标左键 |
mouse_right |
鼠标右键 | mouse_middle |
鼠标中键 |
mouse_scroll_up |
滚轮上滚 | mouse_scroll_down |
滚轮下滚 |
mouse_scroll |
滚轮(占位) |
接入方式(可选):
若项目需要在 UI 中显示按键图标(而非纯文本),将 InputPrompt.asset 设为 TMP 的 Default Sprite Asset:
- 打开 Edit > Project Settings > TextMeshPro
- 在 Default Sprite Asset 中拖入
Assets/Plugins/InputModule/Input_Prompts/InputPrompt.asset
在文本中使用:
按 <sprite="InputPrompt" name=keyboard_e> 交互
按 <sprite="InputPrompt" name=gamepad_a> 确认
与 PromptMap 配合:
if (GameEntry.InputModule.TryGetPrompt(InputActionId.Interact, out InputPrompt prompt))
{
// 文本标签
_label.text = prompt.TextLabel;
// 图标(通过 TMP Sprite Tag)
if (prompt.HasSprite)
{
_iconText.text = $"<sprite=\"InputPrompt\" name={prompt.SpriteName}>";
}
}
说明:
- 此步骤完全可选;不设置 Default Sprite Asset 不影响任何核心功能
- 若项目已有自己的按键图标系统,可直接忽略此文件夹
SpriteName的值由IInputPromptMap实现决定,需与InputPrompt.asset中的 sprite name 保持一致
P5:移动端扩展
架构
移动端输入沿用 P2 确定的边界:
- 普通 UI 点击 / 拖拽 / 滚动 → 继续走
EventSystem / InputSystemUIInputModule - Gameplay 语义输入(Move / Sprint / Interact)→ 通过虚拟摇杆 / 虚拟按钮桥接进
InputModuleComponent
虚拟控件代码位于 Assets/Plugins/InputModule/Presentation/,通过 SepCore.Presentation.InputModule.asmref 编译进 SepCore.Presentation,与基座零耦合。
虚拟摇杆(VirtualJoystickBridge)
组件位置: SepCore.InputModule.Runtime.VirtualInput.VirtualJoystickBridge
挂载要求: 挂在带有 Joystick(或 VariableJoystick / FloatingJoystick / DynamicJoystick)的 GameObject 上。
字段:
| 字段 | 说明 | 默认值 |
|---|---|---|
_actionId |
映射的 Action(如 Move) |
Move |
_contextId |
所属上下文 | GameplayExplore |
_injectDeviceKind |
触摸时是否强制设备类型为 Touch |
true |
使用方式:
- 在 Canvas 下创建空 GameObject
- 挂载
VariableJoystick(或FloatingJoystick/FixedJoystick) - 在同 GameObject 上挂载
VirtualJoystickBridge - 配置
_actionId为Move
// 业务层无需额外代码
// 摇杆输入会自动通过 CommandTriggered / RegisterListener 分发
GameEntry.InputModule.RegisterListener(InputActionId.Move, OnMove);
private void OnMove(InputCommand cmd)
{
Vector2 direction = cmd.Vector2Value;
// 与键盘/手柄输入完全一致
}
虚拟按钮(VirtualButtonBridge)
组件位置: SepCore.InputModule.Runtime.VirtualInput.VirtualButtonBridge
挂载要求: 挂在 UI 按钮(或任意带 Collider2D / RectTransform 的交互元素)上。需要场景中已有 EventSystem。
字段:
| 字段 | 说明 | 默认值 |
|---|---|---|
_actionId |
映射的 Action(如 Sprint / Interact) |
— |
_contextId |
所属上下文 | GameplayExplore |
_injectDeviceKind |
触摸时是否强制设备类型为 Touch |
true |
使用方式:
- 在 Canvas 下创建 UI 按钮 GameObject
- 挂载
VirtualButtonBridge - 配置
_actionId为Sprint或Interact
GameEntry.InputModule.RegisterListener(InputActionId.Sprint, OnSprint);
设备切换
当玩家触摸虚拟摇杆或虚拟按钮时:
VirtualJoystickBridge/VirtualButtonBridge自动调用GameEntry.InputModule.ForceDeviceKind(InputDeviceKind.Touch)- 触发
DeviceKindChanged事件 - UI 层监听该事件,刷新按键提示(如将 "E" 切换为 "Tap")
虚拟控件显示 / 隐藏
由业务层根据 DeviceKindChanged 控制,InputModule 不强制管理:
private void OnDeviceKindChanged(InputDeviceKind kind)
{
_joystickCanvas.SetActive(kind == InputDeviceKind.Touch);
}
推荐策略:
- PC / 主机:隐藏虚拟控件
- 移动端(检测到
Touch):显示虚拟控件 - 键鼠 ↔ 手柄热插拔时不影响虚拟控件显隐(只有
Touch才显示)
预制体
Assets/Plugins/InputModule/Assets/Joystick/Prefabs/ 提供四种摇杆预制体:
Fixed Joystick:固定位置Floating Joystick:按下时于触点浮现Dynamic Joystick:按下时浮现,拖动超过阈值后背景跟随移动Variable Joystick:运行时可通过SetMode(JoystickType)切换以上三种模式
可直接拖入 Canvas 使用。
与 Joystick Pack 的关系
Assets/Plugins/InputModule/Presentation/JoystickPack/ 下的代码是 Joystick Pack 的副本(已修复 AxisOptions 递归属性 bug)。这些代码通过 SepCore.Presentation.InputModule.asmref 编译进 SepCore.Presentation,不修改基座程序集引用。
如果未来需要更新 Joystick Pack:
- 替换
JoystickPack/下的脚本文件 - 重新修复第24行的
AxisOptions属性 bug