Unity 中的输入管理模块:一个键盘按键事件的统一管理方案

        在Unity游戏开发中,处理键盘按键事件是一个常见需求。然而,当一个按键需要触发多个事件,并且这些事件需要按一定顺序执行时,管理这些事件可能变得复杂且容易出错。为了降低系统模块之间的耦合性,并提高代码的可读性和可维护性,我们需要一个统一的输入管理模块。

        本文将介绍如何在 Unity 中实现一个键盘按键事件的统一管理模块。这个模块支持按键按下、抬起、长按和按住等多种事件类型,并且可以通过优先级控制事件的执行顺序。

功能需求

  1. 按键事件的统一管理:一个按键可以绑定多个事件。
  2. 事件优先级:支持按优先级顺序执行事件。
  3. 多种按键事件类型:支持按键按下、抬起、长按和按住等事件类型。
  4. 输入框聚焦检测:在输入框聚焦时,防止按键触发事件(可配置特殊按键除外)。
  5. 删除事件优化:简化删除事件的操作。

代码实现

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键相关逻辑
    }
}

你可能感兴趣的:(Unity功能模块,Unity,C#,solr,lucene,unity,游戏引擎)