ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity MyFramework 用法说明(二十七):使用 InputSystem 与 KeyMappingSystem 统一管理输入和快捷键

Unity MyFramework 用法说明(二十七):使用 InputSystem 与 KeyMappingSystem 统一管理输入和快捷键

角色移动、技能快捷键、编辑器操作和界面关闭都需要读取输入。如果业务代码到处直接调用Input.GetKeyDown(),按键规则、输入焦点和改键功能就会分散在不同系统中。

MyFramework 使用InputSystem统一收集键盘、鼠标和触摸状态,再通过KeyMappingSystem将具体按键映射为游戏行为。

项目地址:

https://github.com/ZHOURUIH/MyFramework

需要注意,这里的InputSystem是 MyFramework 对UnityEngine.Input的封装,不是 Unity 官方的 Input System Package。

一、两个系统分别负责什么

两个系统解决的问题不同:

InputSystem ├── 读取键盘状态 ├── 统一鼠标与触摸输入 ├── 识别单击和双击 ├── 监听固定快捷键 ├── 处理组合键 └── 区分场景输入与输入框输入 KeyMappingSystem ├── 将游戏行为映射到 KeyCode ├── 保存默认按键 ├── 修改当前按键 ├── 检测按键冲突 └── 提供用于界面显示的按键名称

可以简单理解为:

InputSystem 负责“用户按了什么” KeyMappingSystem 负责“这个按键代表什么行为”

二、为什么需要按键映射

直接在移动逻辑中判断:

if (Input.GetKeyDown(KeyCode.W)) { moveUp(); }

意味着“向上移动”这个行为和W键永久绑定。

玩家改成方向键后,需要修改所有使用W的代码。更合理的方式是先定义行为 ID:

public static class KEY_MAPPING { public const int MOVE_UP = 1; public const int MOVE_DOWN = 2; public const int ATTACK = 3; public const int OPEN_PACK = 4; }

业务系统只判断行为:

if (mKeyMappingSystem.isMappingKeyDown( KEY_MAPPING.MOVE_UP)) { moveUp(); }

至于“向上移动”当前对应W还是方向键,由KeyMappingSystem决定。

三、注册默认快捷键

游戏初始化时注册行为和默认按键:

using UnityEngine; using static FrameBaseHotFix; public static class GameKeyMapping { public static void init() { registe( KEY_MAPPING.MOVE_UP, KeyCode.W, "向上移动"); registe( KEY_MAPPING.MOVE_DOWN, KeyCode.S, "向下移动"); registe( KEY_MAPPING.ATTACK, KeyCode.Space, "普通攻击"); registe( KEY_MAPPING.OPEN_PACK, KeyCode.B, "打开背包"); } private static void registe( int mapping, KeyCode key, string actionName) { if (!mKeyMappingSystem.setKeyMapping( mapping, key, actionName)) { return; } mKeyMappingSystem.setDefaultKeyMapping( mapping, key); } }

其中保存了三类信息:

Mapping ID 表示游戏行为 当前按键 表示玩家目前使用的按键 默认按键 用于恢复默认设置

actionName主要用于快捷键设置界面,例如显示:

向上移动 W 普通攻击 Space 打开背包 B

四、区分按下瞬间和持续按住

只在按下的这一帧触发:

if (mKeyMappingSystem.isMappingKeyCurrentDown( KEY_MAPPING.OPEN_PACK)) { LT.LOAD<UIPack>(); }

适合:

打开界面 释放一次技能 确认操作 拾取物品

持续按住期间一直返回true

if (mKeyMappingSystem.isMappingKeyDown( KEY_MAPPING.MOVE_UP)) { moveUp(); }

适合:

角色移动 摄像机移动 持续蓄力 连续操作

两者最终会调用InputSystem

public bool isMappingKeyCurrentDown(int mapping) { return mInputSystem.isKeyCurrentDown( getKeyMapping(mapping), FOCUS_MASK.SCENE); } public bool isMappingKeyDown(int mapping) { return mInputSystem.isKeyDown( getKeyMapping(mapping), FOCUS_MASK.SCENE); }

所以映射按键默认只处理场景输入,不会在输入框中输入文字时误触发游戏快捷键。

五、运行时修改按键

玩家在设置界面中按下一个新键后,可以重新设置映射:

protected bool changeKeyMapping( int mapping, KeyCode newKey) { if (!mKeyMappingSystem.setKeyMapping( mapping, newKey)) { return false; } // 在这里由应用层保存玩家的快捷键配置 return true; }

setKeyMapping()会检查新按键是否已经被其他行为占用:

if (key != KeyCode.None && mKeyMapping.containsValue( item => item.mKey == key && item.mMappingID != mappingID)) { logError("有冲突的按键:" + key); return false; }

例如B已经用于打开背包,再把它设置为普通攻击时,接口会返回false

框架负责映射和冲突检测,玩家按键配置的持久化则由应用层处理。

六、显示当前按键名称

获取某个行为当前绑定的按键:

KeyCode key = mKeyMappingSystem.getKeyMapping( KEY_MAPPING.OPEN_PACK);

获取用于界面显示的名称:

string keyName = mKeyMappingSystem.getKeyMappingName( KEY_MAPPING.OPEN_PACK);

获取行为名称:

string actionName = mKeyMappingSystem.getKeyMappingActionName( KEY_MAPPING.OPEN_PACK);

快捷键设置界面可以直接显示:

mActionText.setText(actionName); mKeyText.setText(keyName);

框架已经为常见按键准备了显示名称,例如:

KeyCode.Space → Space KeyCode.Delete → Del KeyCode.PageUp → PgUp KeyCode.UpArrow → ↑ KeyCode.Keypad1 → Num1

恢复默认设置时,可以读取:

KeyCode defaultKey = mKeyMappingSystem.getDefaultMappingKey(mapping);

再重新调用setKeyMapping()

七、固定快捷键不需要建立映射

并不是所有按键都允许玩家修改。

例如:

Esc 关闭界面 Ctrl + S 保存UGC作品 F1 切换调试信息

这类固定或临时快捷键,可以直接通过InputSystem注册:

public class UGCShortcutSystem : FrameSystem { public override void init() { base.init(); mInputSystem.listenKeyCurrentDown( KeyCode.S, onSave, this, COMBINATION_KEY.CTRL); mInputSystem.listenKeyCurrentDown( KeyCode.Escape, onEscape, this); } public override void destroy() { mInputSystem?.unlistenKey(this); base.destroy(); } protected void onSave() { saveUGCContent(); } protected void onEscape() { closeCurrentPanel(); } protected void saveUGCContent() { } protected void closeCurrentPanel() { } }

监听时需要传入监听者:

this

销毁时可以一次性移除该监听者注册的所有快捷键:

mInputSystem.unlistenKey(this);

避免对象销毁后旧回调继续执行。

八、组合键如何处理

框架支持:

Ctrl Shift Alt

例如监听Ctrl + Shift + S

mInputSystem.listenKeyCurrentDown( KeyCode.S, onSaveAs, this, COMBINATION_KEY.CTRL | COMBINATION_KEY.SHIFT);

InputSystem每帧统计当前组合键状态:

COMBINATION_KEY combination = COMBINATION_KEY.NONE; combination |= isAnyKeyDown( KeyCode.LeftControl, KeyCode.RightControl) ? COMBINATION_KEY.CTRL : COMBINATION_KEY.NONE;

然后与监听信息进行比较:

if (info.mCombinationKey == combination) { info.mCallback?.Invoke(); }

这里使用的是完全匹配。

注册Ctrl + S后,如果当前实际按下的是Ctrl + Shift + S,不会触发这条监听。

九、输入框为什么不会触发场景快捷键

InputSystem保存了当前输入焦点:

protected int mFocusMask;

每帧检查已经注册的输入框:

bool inputting = false; foreach (IInputField item in mInputFieldList) { if (item.isVisible() && item.isFocused()) { inputting = true; break; } } setMask( inputting ? FOCUS_MASK.UI : FOCUS_MASK.SCENE);

查询按键时会检查掩码:

public bool isKeyCurrentDown( KeyCode key, FOCUS_MASK mask = FOCUS_MASK.SCENE) { return mEnableKey && Input.GetKeyDown(key) && hasMask(mask); }

因此,当玩家正在聊天输入框中输入B时,不会同时打开背包。

这比每个业务系统分别判断“当前是否正在输入”更加可靠。

十、统一鼠标和触摸输入

InputSystem将鼠标按键和移动端触点统一保存为:

SafeDictionary<int, TouchPoint>

获取本帧完成的单击:

if (mInputSystem.getTouchClick( out TouchPoint touch)) { Vector3 position = touch.getCurPosition(); }

获取双击:

if (mInputSystem.getTouchDoubleClick( out TouchPoint touch)) { Vector3 position = touch.getCurPosition(); }

获取触点移动量:

TouchPoint point = mInputSystem.getTouchPoint(pointerID); Vector3 moveDelta = point?.getMoveDelta() ?? Vector3.zero;

TouchPoint会记录:

按下位置 当前位置 上一帧位置 本帧移动量 按下与抬起时间 是否单击 是否双击 是否持续按下

桌面端鼠标左、中、右键也会被当作固定触点,因此部分点击逻辑不需要分别编写鼠标版和触屏版。

十一、InputSystem 的每帧流程

InputSystem.update()的主要流程是:

读取鼠标或触屏按下、抬起 ↓ 更新所有 TouchPoint 的位置 ↓ 根据时间和距离识别单击、双击 ↓ 检查当前是否有输入框获得焦点 ↓ 设置 UI 或 SCENE 输入掩码 ↓ 收集本帧按下和抬起的键 ↓ 计算 Ctrl、Shift、Alt 状态 ↓ 分发固定快捷键监听

lateUpdate()再清除当前帧状态:

CurrentDown CurrentUp Click DoubleClick

所以这类状态只在当前帧有效,业务系统应当在正常更新阶段读取。

十二、总结

MyFramework 将输入分为两个层次:

InputSystem 统一采集键盘、鼠标、触摸和输入焦点 KeyMappingSystem 把具体按键转换为可配置的游戏行为

使用原则可以概括为:

玩家可以修改的游戏操作 使用 KeyMappingSystem 固定的系统或调试快捷键 使用 listenKeyCurrentDown 持续操作 使用 isMappingKeyDown 单次操作 使用 isMappingKeyCurrentDown 对象销毁 调用 unlistenKey 输入框正在输入 由 FOCUS_MASK 自动屏蔽场景快捷键

这样角色移动、技能快捷键、UGC 编辑器操作和界面输入可以使用同一套输入状态,同时保持游戏行为与具体按键之间的独立。

返回列表