在Unity游戏开发中,处理键盘按键事件是一个常见需求。然而,当一个按键需要触发多个事件,并且这些事件需要按一定顺序执行时,管理这些事件可能变得复杂且容易出错。为了降低系统模块之间的耦合性,并提高代码的可读性和可维护性,我们需要一个统一的输入管理模块。
本文将介绍如何在 Unity 中实现一个键盘按键事件的统一管理模块。这个模块支持按键按下、抬起、长按和按住等多种事件类型,并且可以通过优先级控制事件的执行顺序。
1. 定义按键事件类型
首先,我们需要定义按键事件类型的枚举:
// 按键事件类型
public enum KeyEventType
{
KeyDown, //按下
KeyUp, //抬起
LongPress, //长按,比如按1秒后触发
KeyHold // 相当于Input.KeyCode
}
2. 事件管理的核心数据结构
我们需要一些核心数据结构来存储按键事件及其优先级信息:
// 事件字典,键是按键,值是每种事件类型的优先级事件列表
private static readonly Dictionary>>> keyEvents = new();
// 当前处理到的优先级字典,键是按键,值是每种事件类型的当前优先级索引
private static readonly Dictionary> currentPriorityIndex = new();
// 特殊处理的按键集合(即使输入框聚焦也要处理)
private static readonly HashSet specialKeys = new();
// 长按检测时间和长按状态
private static readonly Dictionary keyPressTime = new();
private const float LongPressThreshold = 1.0f; // 长按时间阈值(秒)
private static readonly Dictionary isLongPressTriggered = new();
3. 初始化方法
初始化方法负责添加特殊按键并启动按键监听协程:
public static void Initialize()
{
specialKeys.Add(KeyCode.Escape); // ESC键为特殊按键
//携程启动器进行启动监听
GameMonobehaviour.StartCoroutine(KeyListener());
}
4. 按键监听协程
按键监听协程持续检查按键状态并触发相应的事件:
private static IEnumerator KeyListener()
{
while (true)
{
foreach (var key in keyEvents.Keys)
{
HandleKeyEvents(key, KeyEventType.KeyDown, Input.GetKeyDown(key));
HandleKeyEvents(key, KeyEventType.KeyUp, Input.GetKeyUp(key));
HandleKeyHoldAndLongPress(key);
}
yield return null;
}
}
5. 处理按下、抬起事件
根据事件类型触发相应的逻辑:
private static void HandleKeyEvents(KeyCode key, KeyEventType eventType, bool isKeyEvent)
{
if (isKeyEvent)
{
if (IsInputFieldFocused && !specialKeys.Contains(key))
{
return;
}
if (eventType == KeyEventType.KeyDown)
{
keyPressTime[key] = Time.time;
isLongPressTriggered[key] = false;
}
else if (eventType == KeyEventType.KeyUp)
{
keyPressTime.Remove(key);
}
ExecuteNextKeyEvent(key, eventType);
}
}
6. 处理按住和长按事件
检测长按阈值并触发相应的事件:
private static void HandleKeyHoldAndLongPress(KeyCode key)
{
if (Input.GetKey(key) && keyPressTime.ContainsKey(key))
{
if (Time.time - keyPressTime[key] >= LongPressThreshold && !isLongPressTriggered[key])
{
isLongPressTriggered[key] = true;
HandleKeyEvents(key, KeyEventType.LongPress, true);
}
HandleKeyEvents(key, KeyEventType.KeyHold, true);
}
}
7. 检查输入框聚焦
检查是否有输入框被聚焦,防止按键事件在输入框聚焦时触发:
public static bool IsInputFieldFocused
{
get
{
var selected = EventSystem.current.currentSelectedGameObject;
if (selected != null)
{
return selected.GetComponent() != null || selected.GetComponent() != null;
}
return false;
}
}
8. 注册和删除事件
注册按键事件,支持优先级管理;优化后的删除事件只需传入按键和回调方法:
// 注册事件,带优先级,priority 越高,优先级越高
public static void RegisterKeyEvent(KeyCode key, Action action, int priority = 0, KeyEventType eventType = KeyEventType.KeyDown)
{
if (!keyEvents.ContainsKey(key))
{
keyEvents[key] = new Dictionary>>();
currentPriorityIndex[key] = new Dictionary();
}
if (!keyEvents[key].ContainsKey(eventType))
{
keyEvents[key][eventType] = new SortedList>();
currentPriorityIndex[key][eventType] = -1; // 初始化当前优先级索引为-1
}
if (!keyEvents[key][eventType].ContainsKey(priority))
{
keyEvents[key][eventType][priority] = new List();
}
keyEvents[key][eventType][priority].Add(action);
}
// 删除事件
public static void UnregisterKeyEvent(KeyCode key, Action action)
{
if (keyEvents.ContainsKey(key))
{
foreach (var eventType in keyEvents[key].Keys.ToList())
{
foreach (var priority in keyEvents[key][eventType].Keys.ToList())
{
keyEvents[key][eventType][priority].Remove(action);
if (keyEvents[key][eventType][priority].Count == 0)
{
keyEvents[key][eventType].Remove(priority);
}
}
if (keyEvents[key][eventType].Count == 0)
{
keyEvents[key].Remove(eventType);
currentPriorityIndex[key].Remove(eventType);
}
}
if (keyEvents[key].Count == 0)
{
keyEvents.Remove(key);
currentPriorityIndex.Remove(key);
}
}
}
9. 执行下一个按键事件
执行下一个优先级的按键事件,并在所有优先级执行完毕后重置优先级索引:
private static void ExecuteNextKeyEvent(KeyCode key, KeyEventType eventType)
{
if (keyEvents.ContainsKey(key) && keyEvents[key].ContainsKey(eventType))
{
var events = keyEvents[key][eventType];
var currentPriority = currentPriorityIndex[key][eventType];
var priorities = events.Keys;
// 找到下一个要执行的优先级
for (int i = priorities.Count - 1; i >= 0; i--)
{
if (priorities[i] > currentPriority)
{
currentPriority = priorities[i];
currentPriorityIndex[key][eventType] = currentPriority;
break;
}
}
// 执行当前优先级的事件
if (events.ContainsKey(currentPriority))
{
foreach (var action in events[currentPriority])
{
action?.Invoke();
}
}
// 重置优先级索引以循环执行
if (currentPriority == priorities[0])
{
currentPriorityIndex[key][eventType] = -1;
}
}
}
通过以上代码,我们实现了一个功能齐全的输入管理模块,支持多种按键事件类型和优先级管理。这个模块不仅降低了系统模块之间的耦合性,还提高了代码的可读性和可维护性。希望这篇文章能对您的 Unity 开发有所帮助。如果有任何问题或建议,欢迎在评论区讨论!
假设你有一个模块,按下ESC键可以取消输入框的焦点,并且有其他操作,同时处理按键抬起和长按事件
using UnityEngine;
using UnityEngine.EventSystems;
public class EscapeKeyHandler : MonoBehaviour
{
private void Start()
{
GameMonobehaviour.Behaviour = this;
InputManager.Initialize();
// 注册Esc键按下事件,优先级为1
InputManager.RegisterKeyEvent(KeyCode.Escape, ClearInputFieldFocus, 1, InputManager.KeyEventType.KeyDown);
// 注册Esc键按下事件,优先级为2
InputManager.RegisterKeyEvent(KeyCode.Escape, SomeOtherEscapeAction, 2, InputManager.KeyEventType.KeyDown);
// 注册Esc键长按事件,优先级为1
InputManager.RegisterKeyEvent(KeyCode.Escape, LongPressEscapeAction, 1, InputManager.KeyEventType.LongPress);
// 注册Esc键抬起事件,优先级为1
InputManager.RegisterKeyEvent(KeyCode.Escape, KeyUpEscapeAction, 1, InputManager.KeyEventType.KeyUp);
}
private void OnDestroy()
{
// 注销事件
InputManager.UnregisterKeyEvent(KeyCode.Escape, ClearInputFieldFocus);
InputManager.UnregisterKeyEvent(KeyCode.Escape, SomeOtherEscapeAction);
InputManager.UnregisterKeyEvent(KeyCode.Escape, LongPressEscapeAction);
InputManager.UnregisterKeyEvent(KeyCode.Escape, KeyUpEscapeAction);
}
private void ClearInputFieldFocus()
{
Debug.Log("Clearing input field focus");
EventSystem.current.SetSelectedGameObject(null);
}
private void SomeOtherEscapeAction()
{
Debug.Log("Executing other ESC action");
// 其他ESC键相关逻辑
}
private void LongPressEscapeAction()
{
Debug.Log("Executing long press ESC action");
// 长按ESC键相关逻辑
}
private void KeyUpEscapeAction()
{
Debug.Log("Executing key up ESC action");
// 抬起ESC键相关逻辑
}
}