10.输入模块
10.1 知识点
输入模块的作用
在游戏开发中,我们经常需要处理键盘和鼠标输入。传统的做法是在各个脚本的Update中直接调用Unity的Input类进行检测,这会导致以下问题:
核心问题:
- 代码耦合性高:输入检测逻辑分散在各个模块中
- 重复代码多:每个模块都需要重复编写输入检测代码
- 难以改键:需要修改多个地方的代码才能改变按键映射
- 缺乏统一管理:无法统一控制输入系统的开启和关闭
解决方案:
通过独立的输入管理模块,统一管理所有键盘和鼠标输入,利用事件中心分发输入事件,实现输入逻辑与业务逻辑的解耦。
主要功能:
- 统一输入管理:在输入管理器中进行所有按键检测
- 事件分发:通过事件中心分发输入事件
- 支持改键功能:可以动态修改按键映射
- 输入检测开关:提供开启和关闭输入检测的功能
- 获取输入信息:支持获取任意按键的输入信息(用于改键)
输入模块的基本原理
设计理念:
- 集中管理:统一管理所有键盘和鼠标输入
- 事件驱动:通过事件中心实现输入与业务逻辑的解耦
- 可配置性:支持动态配置和修改按键映射
- 灵活扩展:支持键盘、鼠标和Unity Axes输入
工作流程:
- 输入检测:在InputMgr的Update中检测键盘和鼠标输入
- 事件映射:根据配置的按键映射判断触发哪个事件
- 事件分发:通过事件中心分发对应的输入事件
- 业务处理:各个模块监听对应的输入事件并处理业务逻辑
输入模块基础实现
为什么需要输入模块
传统输入处理方式的问题:
// 传统方式:在各个脚本中直接检测输入
public class Player : MonoBehaviour
{
void Update()
{
// 移动逻辑
if (Input.GetKey(KeyCode.W))
{
transform.Translate(Vector3.forward * Time.deltaTime);
}
if (Input.GetKey(KeyCode.S))
{
transform.Translate(Vector3.back * Time.deltaTime);
}
// 攻击逻辑
if (Input.GetKeyDown(KeyCode.Space))
{
Attack();
}
// 技能逻辑
if (Input.GetKeyDown(KeyCode.Alpha1))
{
UseSkill1();
}
}
}
存在的问题:
- 高耦合:业务逻辑与输入检测耦合在一起
- 难以扩展:添加新角色需要重复编写输入检测代码
- 难以改键:需要修改多处代码才能改变按键
- 难以测试:输入逻辑与业务逻辑混合,难以独立测试
输入模块基本实现
实现思路:
- 制作InputMgr单例模式管理器:统一管理所有输入检测
- 在Update中检测按键:检测常用按键(W、A、S、D、鼠标按键等)
- 通过事件中心分发事件:输入检测到后触发相应事件
- 业务模块监听事件:各个模块监听输入事件并处理业务逻辑
基础源码实现:InputMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class InputMgr : Singleton<InputMgr>
{
//是否开启了输入系统检测
private bool isStart;
private InputMgr()
{
MonoMgr.Instance.AddUpdateListener(InputUpdate);
}
/// <summary>
/// 开启或者关闭我们的输入管理模块的检测
/// </summary>
/// <param name="isStart">是否开启检测</param>
public void StartOrCloseInputMgr(bool isStart)
{
this.isStart = isStart;
}
/// <summary>
/// 输入检测更新
/// </summary>
private void InputUpdate()
{
//如果外部没有开启检测功能 就不要检测
if (!isStart)
return;
// 检测键盘按键
CheckKeyCode(KeyCode.W);
CheckKeyCode(KeyCode.A);
CheckKeyCode(KeyCode.S);
CheckKeyCode(KeyCode.D);
CheckKeyCode(KeyCode.H);
CheckKeyCode(KeyCode.J);
CheckKeyCode(KeyCode.K);
CheckKeyCode(KeyCode.L);
// 检测鼠标按键
CheckMouse(0); // 左键
CheckMouse(1); // 右键
// 检测Unity Axes
EventCenter.Instance.EventTrigger<float>(E_EventType.E_Input_Horizontal, Input.GetAxis("Horizontal"));
EventCenter.Instance.EventTrigger<float>(E_EventType.E_Input_Vertical, Input.GetAxis("Vertical"));
}
/// <summary>
/// 检测键盘按键
/// </summary>
/// <param name="key">按键代码</param>
private void CheckKeyCode(KeyCode key)
{
// 按下
if (Input.GetKeyDown(key))
{
EventCenter.Instance.EventTrigger(E_EventType.E_Keyboard_Down, key);
}
// 抬起
if (Input.GetKeyUp(key))
{
EventCenter.Instance.EventTrigger(E_EventType.E_Keyboard_Up, key);
}
// 长按
if (Input.GetKey(key))
{
EventCenter.Instance.EventTrigger(E_EventType.E_Keyboard, key);
}
}
/// <summary>
/// 检测鼠标按键
/// </summary>
/// <param name="mouseID">鼠标按键ID(0=左键,1=右键,2=中键)</param>
private void CheckMouse(int mouseID)
{
// 按下
if (Input.GetMouseButtonDown(mouseID))
{
EventCenter.Instance.EventTrigger(E_EventType.E_Mouse_Down, mouseID);
}
// 抬起
if (Input.GetMouseButtonUp(mouseID))
{
EventCenter.Instance.EventTrigger(E_EventType.E_Mouse_Up, mouseID);
}
// 长按
if (Input.GetMouseButton(mouseID))
{
EventCenter.Instance.EventTrigger(E_EventType.E_Mouse, mouseID);
}
}
}
使用示例:
using UnityEngine;
public class InputModuleTest : MonoBehaviour
{
void Start()
{
Debug.Log("输入模块测试:开始");
// 开启输入检测
InputMgr.Instance.StartOrCloseInputMgr(true);
// 监听键盘按键事件
EventCenter.Instance.AddEventListener<KeyCode>(E_EventType.E_Keyboard_Down, OnKeyDown);
EventCenter.Instance.AddEventListener<KeyCode>(E_EventType.E_Keyboard_Up, OnKeyUp);
EventCenter.Instance.AddEventListener<KeyCode>(E_EventType.E_Keyboard, OnKeyPressed);
// 监听鼠标事件
EventCenter.Instance.AddEventListener<int>(E_EventType.E_Mouse_Down, OnMouseDown);
// 监听Axis事件
EventCenter.Instance.AddEventListener<float>(E_EventType.E_Input_Horizontal, OnHorizontalMove);
EventCenter.Instance.AddEventListener<float>(E_EventType.E_Input_Vertical, OnVerticalMove);
}
private void OnKeyDown(KeyCode key)
{
Debug.Log($"键盘按下:{key}");
// 输出:键盘按下:W
}
private void OnKeyUp(KeyCode key)
{
Debug.Log($"键盘抬起:{key}");
// 输出:键盘抬起:W
}
private void OnKeyPressed(KeyCode key)
{
Debug.Log($"键盘长按:{key}");
// 输出:键盘长按:W(每帧)
}
private void OnMouseDown(int mouseID)
{
Debug.Log($"鼠标按下:{mouseID}");
// 输出:鼠标按下:0(左键)
}
private void OnHorizontalMove(float value)
{
// 水平移动(-1~1)
Debug.Log($"水平输入:{value}");
}
private void OnVerticalMove(float value)
{
// 垂直移动(-1~1)
Debug.Log($"垂直输入:{value}");
}
void OnDestroy()
{
// 移除事件监听
EventCenter.Instance.RemoveEventListener<KeyCode>(E_EventType.E_Keyboard_Down, OnKeyDown);
EventCenter.Instance.RemoveEventListener<KeyCode>(E_EventType.E_Keyboard_Up, OnKeyUp);
EventCenter.Instance.RemoveEventListener<KeyCode>(E_EventType.E_Keyboard, OnKeyPressed);
EventCenter.Instance.RemoveEventListener<int>(E_EventType.E_Mouse_Down, OnMouseDown);
EventCenter.Instance.RemoveEventListener<float>(E_EventType.E_Input_Horizontal, OnHorizontalMove);
EventCenter.Instance.RemoveEventListener<float>(E_EventType.E_Input_Vertical, OnVerticalMove);
}
}
基础实现的局限性
存在的问题:
- 按键写死:只能检测固定的几个按键
- 无法改键:想要改变按键映射需要修改代码
- 行为固定:每个按键都只能触发特定的事件类型
- 缺乏灵活性:无法根据游戏需求灵活配置按键
输入模块改键功能优化
为什么需要改键功能
应用场景:
在游戏中,玩家希望能够自定义按键映射:
- 角色移动:WASD、方向键或其他按键
- 技能释放:数字键1-9或其他按键
- 攻击按键:鼠标左键、右键或其他按键
- 交互按键:E键、F键或其他按键
需求分析:
- 改键功能应该是针对某一个行为的:游戏中的行为是固定的,我们需要改变的是触发该行为的按键
- 具体键位的触发不应该写死:需要根据存储的键位数据进行初始化或进行修改
- 键位的修改可以是键盘输入,也可以是鼠标输入,输入类型也可以是任意的
实现改键功能
设计思路:
- 创建InputInfo类:用于存储按键信息(键盘或鼠标、按下或抬起或长按、具体按键)
- 使用字典存储映射:Dictionary<事件类型, InputInfo>
- 提供配置方法:提供ChangeKeyboardInfo和ChangeMouseInfo方法用于配置按键
- 动态检测输入:根据配置的映射关系动态检测输入
InputInfo实现:InputInfo.cs
using UnityEngine;
/// <summary>
/// 输入信息类
/// 用于存储和管理键盘和鼠标的输入信息
/// </summary>
public class InputInfo
{
#region 枚举
/// <summary>
/// 键盘或鼠标枚举
/// </summary>
public enum E_KeyOrMouse
{
/// <summary>
/// 键盘输入
/// </summary>
Key,
/// <summary>
/// 鼠标输入
/// </summary>
Mouse,
}
/// <summary>
/// 输入类型枚举
/// </summary>
public enum E_InputType
{
/// <summary>
/// 按下
/// </summary>
Down,
/// <summary>
/// 抬起
/// </summary>
Up,
/// <summary>
/// 长按
/// </summary>
Always,
}
#endregion
#region 字段
/// <summary>
/// 具体输入的类型——键盘还是鼠标
/// </summary>
public E_KeyOrMouse keyOrMouse;
/// <summary>
/// 输入的类型——抬起、按下、长按
/// </summary>
public E_InputType inputType;
/// <summary>
/// KeyCode
/// </summary>
public KeyCode key;
/// <summary>
/// mouseID
/// </summary>
public int mouseID;
#endregion
#region 构造函数
/// <summary>
/// 主要给键盘输入初始化
/// </summary>
/// <param name="inputType">输入类型</param>
/// <param name="key">按键代码</param>
public InputInfo(E_InputType inputType, KeyCode key)
{
this.keyOrMouse = E_KeyOrMouse.Key;
this.inputType = inputType;
this.key = key;
}
/// <summary>
/// 主要给鼠标输入初始化
/// </summary>
/// <param name="inputType">输入类型</param>
/// <param name="mouseID">鼠标按键ID</param>
public InputInfo(E_InputType inputType, int mouseID)
{
this.keyOrMouse = E_KeyOrMouse.Mouse;
this.inputType = inputType;
this.mouseID = mouseID;
}
#endregion
}
InputMgr改键功能实现:InputMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class InputMgr : Singleton<InputMgr>
{
/// <summary>
/// 输入信息字典
/// 键:事件类型
/// 值:输入信息
/// </summary>
private Dictionary<E_EventType, InputInfo> inputDic = new Dictionary<E_EventType, InputInfo>();
/// <summary>
/// 当前遍历时取出的输入信息
/// </summary>
private InputInfo nowInputInfo;
/// <summary>
/// 是否开启了输入系统检测
/// </summary>
private bool isStart;
private InputMgr()
{
MonoMgr.Instance.AddUpdateListener(InputUpdate);
}
/// <summary>
/// 开启或者关闭我们的输入管理模块的检测
/// </summary>
/// <param name="isStart">是否开启检测</param>
public void StartOrCloseInputMgr(bool isStart)
{
this.isStart = isStart;
}
/// <summary>
/// 提供给外部改键或初始化的方法(键盘)
/// </summary>
/// <param name="eventType">事件类型</param>
/// <param name="key">按键代码</param>
/// <param name="inputType">输入类型</param>
public void ChangeKeyboardInfo(E_EventType eventType, KeyCode key, InputInfo.E_InputType inputType)
{
// 初始化
if (!inputDic.ContainsKey(eventType))
{
inputDic.Add(eventType, new InputInfo(inputType, key));
}
else // 改键
{
// 如果之前是鼠标 我们必须要修改它的按键类型
inputDic[eventType].keyOrMouse = InputInfo.E_KeyOrMouse.Key;
inputDic[eventType].key = key;
inputDic[eventType].inputType = inputType;
}
}
/// <summary>
/// 提供给外部改键或初始化的方法(鼠标)
/// </summary>
/// <param name="eventType">事件类型</param>
/// <param name="mouseID">鼠标按键ID</param>
/// <param name="inputType">输入类型</param>
public void ChangeMouseInfo(E_EventType eventType, int mouseID, InputInfo.E_InputType inputType)
{
// 初始化
if (!inputDic.ContainsKey(eventType))
{
inputDic.Add(eventType, new InputInfo(inputType, mouseID));
}
else // 改键
{
// 如果之前是鼠标 我们必须要修改它的按键类型
inputDic[eventType].keyOrMouse = InputInfo.E_KeyOrMouse.Mouse;
inputDic[eventType].mouseID = mouseID;
inputDic[eventType].inputType = inputType;
}
}
/// <summary>
/// 移除指定行为的输入监听
/// </summary>
/// <param name="eventType">事件类型</param>
public void RemoveInputInfo(E_EventType eventType)
{
if (inputDic.ContainsKey(eventType))
inputDic.Remove(eventType);
}
/// <summary>
/// 输入检测更新
/// </summary>
private void InputUpdate()
{
// 如果外部没有开启检测功能 就不要检测
if (!isStart)
return;
// 遍历配置的输入映射
foreach (E_EventType eventType in inputDic.Keys)
{
nowInputInfo = inputDic[eventType];
// 如果是键盘输入
if (nowInputInfo.keyOrMouse == InputInfo.E_KeyOrMouse.Key)
{
// 是抬起还是按下还是长按
switch (nowInputInfo.inputType)
{
case InputInfo.E_InputType.Down:
if (Input.GetKeyDown(nowInputInfo.key))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Up:
if (Input.GetKeyUp(nowInputInfo.key))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Always:
if (Input.GetKey(nowInputInfo.key))
EventCenter.Instance.EventTrigger(eventType);
break;
default:
break;
}
}
// 如果是鼠标输入
else
{
switch (nowInputInfo.inputType)
{
case InputInfo.E_InputType.Down:
if (Input.GetMouseButtonDown(nowInputInfo.mouseID))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Up:
if (Input.GetMouseButtonUp(nowInputInfo.mouseID))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Always:
if (Input.GetMouseButton(nowInputInfo.mouseID))
EventCenter.Instance.EventTrigger(eventType);
break;
default:
break;
}
}
}
// 始终检测Unity Axes
EventCenter.Instance.EventTrigger<float>(E_EventType.E_Input_Horizontal, Input.GetAxis("Horizontal"));
EventCenter.Instance.EventTrigger<float>(E_EventType.E_Input_Vertical, Input.GetAxis("Vertical"));
}
}
改键功能使用示例:
using UnityEngine;
public class KeyRemappingTest : MonoBehaviour
{
void Start()
{
Debug.Log("改键功能测试:开始");
// 开启输入检测
InputMgr.Instance.StartOrCloseInputMgr(true);
// 配置初始按键映射
InitializeKeyMapping();
// 监听技能事件
EventCenter.Instance.AddEventListener(E_EventType.E_Input_Skill1, OnSkill1);
EventCenter.Instance.AddEventListener(E_EventType.E_Input_Skill2, OnSkill2);
EventCenter.Instance.AddEventListener(E_EventType.E_Input_Skill3, OnSkill3);
// 延迟3秒后测试改键
Invoke("TestKeyRemapping", 3f);
}
/// <summary>
/// 初始化按键映射
/// </summary>
private void InitializeKeyMapping()
{
// 技能1绑定到数字键1
InputMgr.Instance.ChangeKeyboardInfo(E_EventType.E_Input_Skill1, KeyCode.Alpha1, InputInfo.E_InputType.Down);
// 技能2绑定到数字键2
InputMgr.Instance.ChangeKeyboardInfo(E_EventType.E_Input_Skill2, KeyCode.Alpha2, InputInfo.E_InputType.Down);
// 技能3绑定到数字键3
InputMgr.Instance.ChangeKeyboardInfo(E_EventType.E_Input_Skill3, KeyCode.Alpha3, InputInfo.E_InputType.Down);
Debug.Log("初始按键映射配置完成");
// 输出:初始按键映射配置完成
}
private void OnSkill1()
{
Debug.Log("技能1释放");
// 输出:技能1释放
}
private void OnSkill2()
{
Debug.Log("技能2释放");
// 输出:技能2释放
}
private void OnSkill3()
{
Debug.Log("技能3释放");
// 输出:技能3释放
}
/// <summary>
/// 测试改键功能
/// </summary>
private void TestKeyRemapping()
{
Debug.Log("测试改键功能");
// 改键:将技能1改为鼠标左键
InputMgr.Instance.ChangeMouseInfo(E_EventType.E_Input_Skill1, 0, InputInfo.E_InputType.Down);
Debug.Log("技能1已改为鼠标左键");
// 改键:将技能2改为Q键
InputMgr.Instance.ChangeKeyboardInfo(E_EventType.E_Input_Skill2, KeyCode.Q, InputInfo.E_InputType.Down);
Debug.Log("技能2已改为Q键");
// 改键:将技能3改为长按E键
InputMgr.Instance.ChangeKeyboardInfo(E_EventType.E_Input_Skill3, KeyCode.E, InputInfo.E_InputType.Always);
Debug.Log("技能3已改为长按E键");
}
void OnDestroy()
{
// 移除事件监听
EventCenter.Instance.RemoveEventListener(E_EventType.E_Input_Skill1, OnSkill1);
EventCenter.Instance.RemoveEventListener(E_EventType.E_Input_Skill2, OnSkill2);
EventCenter.Instance.RemoveEventListener(E_EventType.E_Input_Skill3, OnSkill3);
}
}
输入模块获取输入信息优化
为什么需要获取输入信息功能
应用场景:
在做改键功能时,我们需要让玩家按下任意按键来重新绑定。这时就需要能够获取用户按下的任意按键信息。
需求分析:
- 获取任意键盘按键:遍历所有KeyCode,检测哪个按键被按下
- 获取任意鼠标按键:检测鼠标左、中、右键哪个被按下
- 延迟一帧检测:避免开启检测时立刻触发当前的输入
实现思路:
- 遍历KeyCode枚举:使用
Enum.GetValues(typeof(KeyCode))获取所有按键 - 逐帧检测输入:只有当
Input.anyKeyDown为true时才开始检测 - 延迟一帧:使用协程延迟一帧再开始检测,避免立即触发
- 返回输入信息:通过委托将检测到的输入信息返回给外部
获取输入信息功能实现
InputMgr增强版:InputMgr.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class InputMgr : Singleton<InputMgr>
{
private Dictionary<E_EventType, InputInfo> inputDic = new Dictionary<E_EventType, InputInfo>();
// 当前遍历时取出的输入信息
private InputInfo nowInputInfo;
// 是否开启了输入系统检测
private bool isStart;
// 用于在改键时获取输入信息的委托 只有当update中获取到信息的时候 再通过委托传递给外部
private UnityAction<InputInfo> getInputInfoCallBack;
// 是否开始检测输入信息
private bool isBeginCheckInput = false;
private InputMgr()
{
MonoMgr.Instance.AddUpdateListener(InputUpdate);
}
/// <summary>
/// 开启或者关闭我们的输入管理模块的检测
/// </summary>
/// <param name="isStart">是否开启检测</param>
public void StartOrCloseInputMgr(bool isStart)
{
this.isStart = isStart;
}
/// <summary>
/// 提供给外部改键或初始化的方法(键盘)
/// </summary>
public void ChangeKeyboardInfo(E_EventType eventType, KeyCode key, InputInfo.E_InputType inputType)
{
// 初始化
if (!inputDic.ContainsKey(eventType))
{
inputDic.Add(eventType, new InputInfo(inputType, key));
}
else // 改键
{
inputDic[eventType].keyOrMouse = InputInfo.E_KeyOrMouse.Key;
inputDic[eventType].key = key;
inputDic[eventType].inputType = inputType;
}
}
/// <summary>
/// 提供给外部改键或初始化的方法(鼠标)
/// </summary>
public void ChangeMouseInfo(E_EventType eventType, int mouseID, InputInfo.E_InputType inputType)
{
// 初始化
if (!inputDic.ContainsKey(eventType))
{
inputDic.Add(eventType, new InputInfo(inputType, mouseID));
}
else // 改键
{
inputDic[eventType].keyOrMouse = InputInfo.E_KeyOrMouse.Mouse;
inputDic[eventType].mouseID = mouseID;
inputDic[eventType].inputType = inputType;
}
}
/// <summary>
/// 移除指定行为的输入监听
/// </summary>
/// <param name="eventType">事件类型</param>
public void RemoveInputInfo(E_EventType eventType)
{
if (inputDic.ContainsKey(eventType))
inputDic.Remove(eventType);
}
/// <summary>
/// 获取下一次的输入信息
/// 用于改键功能中,等待玩家按下任意按键
/// </summary>
/// <param name="callBack">输入信息回调</param>
public void GetInputInfo(UnityAction<InputInfo> callBack)
{
getInputInfoCallBack = callBack;
MonoMgr.Instance.StartCoroutine(BeginCheckInput());
}
/// <summary>
/// 延迟一帧开始检测输入
/// 避免开启检测时立刻触发当前的输入
/// </summary>
private IEnumerator BeginCheckInput()
{
// 等一帧
yield return 0;
// 一帧后才会被置成true
isBeginCheckInput = true;
}
/// <summary>
/// 输入检测更新
/// </summary>
private void InputUpdate()
{
// 当委托不为空时 证明想要获取到输入的信息 传递给外部
if (isBeginCheckInput)
{
// 当一个键按下时 然后遍历所有按键信息 得到是谁被按下了
if (Input.anyKeyDown)
{
InputInfo inputInfo = null;
// 我们需要去遍历监听所有键位的按下 来得到对应输入的信息
// 键盘
Array keyCodes = Enum.GetValues(typeof(KeyCode));
foreach (KeyCode inputKey in keyCodes)
{
// 判断到底是谁被按下了 那么就可以得到对应的输入的键盘信息
if (Input.GetKeyDown(inputKey))
{
inputInfo = new InputInfo(InputInfo.E_InputType.Down, inputKey);
break;
}
}
// 鼠标
if (inputInfo == null)
{
for (int i = 0; i < 3; i++)
{
if (Input.GetMouseButtonDown(i))
{
inputInfo = new InputInfo(InputInfo.E_InputType.Down, i);
break;
}
}
}
// 把获取到的信息传递给外部
getInputInfoCallBack?.Invoke(inputInfo);
getInputInfoCallBack = null;
// 检测一次后就停止检测了
isBeginCheckInput = false;
}
}
// 如果外部没有开启检测功能 就不要检测
if (!isStart)
return;
// 遍历配置的输入映射并检测输入
foreach (E_EventType eventType in inputDic.Keys)
{
nowInputInfo = inputDic[eventType];
// 如果是键盘输入
if (nowInputInfo.keyOrMouse == InputInfo.E_KeyOrMouse.Key)
{
// 是抬起还是按下还是长按
switch (nowInputInfo.inputType)
{
case InputInfo.E_InputType.Down:
if (Input.GetKeyDown(nowInputInfo.key))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Up:
if (Input.GetKeyUp(nowInputInfo.key))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Always:
if (Input.GetKey(nowInputInfo.key))
EventCenter.Instance.EventTrigger(eventType);
break;
default:
break;
}
}
// 如果是鼠标输入
else
{
switch (nowInputInfo.inputType)
{
case InputInfo.E_InputType.Down:
if (Input.GetMouseButtonDown(nowInputInfo.mouseID))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Up:
if (Input.GetMouseButtonUp(nowInputInfo.mouseID))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Always:
if (Input.GetMouseButton(nowInputInfo.mouseID))
EventCenter.Instance.EventTrigger(eventType);
break;
default:
break;
}
}
}
// 始终检测Unity Axes
EventCenter.Instance.EventTrigger<float>(E_EventType.E_Input_Horizontal, Input.GetAxis("Horizontal"));
EventCenter.Instance.EventTrigger<float>(E_EventType.E_Input_Vertical, Input.GetAxis("Vertical"));
}
}
获取输入信息功能使用示例:
using UnityEngine;
public class GetInputInfoTest : MonoBehaviour
{
void Start()
{
Debug.Log("获取输入信息测试:开始");
// 测试获取输入信息
TestGetInputInfo();
}
/// <summary>
/// 测试获取输入信息
/// </summary>
private void TestGetInputInfo()
{
Debug.Log("等待用户按下任意按键...");
// 获取下一个按键输入
InputMgr.Instance.GetInputInfo((inputInfo) =>
{
if (inputInfo != null)
{
if (inputInfo.keyOrMouse == InputInfo.E_KeyOrMouse.Key)
{
Debug.Log($"检测到键盘按键:{inputInfo.key}");
// 输出:检测到键盘按键:Q
}
else
{
Debug.Log($"检测到鼠标按键:{inputInfo.mouseID}");
// 输出:检测到鼠标按键:0
}
// 可以根据获取到的输入信息进行改键
OnKeyRemap(inputInfo);
}
else
{
Debug.Log("未检测到有效输入");
}
});
}
/// <summary>
/// 根据输入信息进行改键
/// </summary>
private void OnKeyRemap(InputInfo inputInfo)
{
Debug.Log("开始改键...");
// 根据获取到的输入信息改键技能1
if (inputInfo.keyOrMouse == InputInfo.E_KeyOrMouse.Key)
{
InputMgr.Instance.ChangeKeyboardInfo(
E_EventType.E_Input_Skill1,
inputInfo.key,
InputInfo.E_InputType.Down
);
Debug.Log($"技能1已改键为:{inputInfo.key}");
}
else
{
InputMgr.Instance.ChangeMouseInfo(
E_EventType.E_Input_Skill1,
inputInfo.mouseID,
InputInfo.E_InputType.Down
);
Debug.Log($"技能1已改键为:鼠标{inputInfo.mouseID}");
}
Debug.Log("改键完成");
}
}
测试和使用
创建一个完整的改键系统,结合UI模块实现可视化改键:
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 改键系统
/// 演示如何使用InputMgr实现完整的改键功能
/// </summary>
public class KeyRemappingSystem : MonoBehaviour
{
private Button skill1Button;
private Button skill2Button;
private Button skill3Button;
private Text skill1Text;
private Text skill2Text;
private Text skill3Text;
void Start()
{
Debug.Log("改键系统测试:开始");
// 开启输入检测
InputMgr.Instance.StartOrCloseInputMgr(true);
// 初始化按键映射
InitializeKeyMapping();
// 监听技能事件
ListenToSkillEvents();
// 初始化UI(假设已经通过GetControl获取到按钮)
InitializeUI();
}
/// <summary>
/// 初始化按键映射
/// </summary>
private void InitializeKeyMapping()
{
// 从配置文件读取按键映射(这里简化为直接设置)
InputMgr.Instance.ChangeKeyboardInfo(
E_EventType.E_Input_Skill1,
KeyCode.Alpha1,
InputInfo.E_InputType.Down
);
InputMgr.Instance.ChangeKeyboardInfo(
E_EventType.E_Input_Skill2,
KeyCode.Alpha2,
InputInfo.E_InputType.Down
);
InputMgr.Instance.ChangeKeyboardInfo(
E_EventType.E_Input_Skill3,
KeyCode.Alpha3,
InputInfo.E_InputType.Down
);
Debug.Log("按键映射初始化完成");
}
/// <summary>
/// 监听技能事件
/// </summary>
private void ListenToSkillEvents()
{
EventCenter.Instance.AddEventListener(E_EventType.E_Input_Skill1, OnSkill1);
EventCenter.Instance.AddEventListener(E_EventType.E_Input_Skill2, OnSkill2);
EventCenter.Instance.AddEventListener(E_EventType.E_Input_Skill3, OnSkill3);
}
private void OnSkill1()
{
Debug.Log("技能1释放");
}
private void OnSkill2()
{
Debug.Log("技能2释放");
}
private void OnSkill3()
{
Debug.Log("技能3释放");
}
/// <summary>
/// 初始化UI
/// </summary>
private void InitializeUI()
{
Debug.Log("UI初始化完成");
// 实际项目中会在这里获取按钮和文本组件
}
/// <summary>
/// 改键按钮点击事件
/// </summary>
public void OnRemapSkill1()
{
RemapKey(E_EventType.E_Input_Skill1, (keyName) =>
{
skill1Text.text = keyName;
});
}
public void OnRemapSkill2()
{
RemapKey(E_EventType.E_Input_Skill2, (keyName) =>
{
skill2Text.text = keyName;
});
}
public void OnRemapSkill3()
{
RemapKey(E_EventType.E_Input_Skill3, (keyName) =>
{
skill3Text.text = keyName;
});
}
/// <summary>
/// 改键流程
/// </summary>
/// <param name="eventType">要改键的事件类型</param>
/// <param name="updateUI">更新UI的回调</param>
private void RemapKey(E_EventType eventType, UnityAction<string> updateUI)
{
Debug.Log($"准备改键:{eventType}");
// 1. 获取用户按下的下一个按键
InputMgr.Instance.GetInputInfo((inputInfo) =>
{
if (inputInfo == null)
{
Debug.Log("未检测到有效输入");
return;
}
// 2. 根据输入信息进行改键
if (inputInfo.keyOrMouse == InputInfo.E_KeyOrMouse.Key)
{
InputMgr.Instance.ChangeKeyboardInfo(
eventType,
inputInfo.key,
InputInfo.E_InputType.Down
);
updateUI?.Invoke(inputInfo.key.ToString());
Debug.Log($"{eventType}已改键为:{inputInfo.key}");
}
else
{
string mouseName = inputInfo.mouseID == 0 ? "左键" :
inputInfo.mouseID == 1 ? "右键" : "中键";
InputMgr.Instance.ChangeMouseInfo(
eventType,
inputInfo.mouseID,
InputInfo.E_InputType.Down
);
updateUI?.Invoke($"鼠标{mouseName}");
Debug.Log($"{eventType}已改键为:鼠标{mouseName}");
}
});
}
void OnDestroy()
{
// 移除事件监听
EventCenter.Instance.RemoveEventListener(E_EventType.E_Input_Skill1, OnSkill1);
EventCenter.Instance.RemoveEventListener(E_EventType.E_Input_Skill2, OnSkill2);
EventCenter.Instance.RemoveEventListener(E_EventType.E_Input_Skill3, OnSkill3);
}
}
进阶和拓展
进阶优化建议:
- 数据持久化:将按键配置保存到本地,下次启动时自动加载
- 多按键支持:支持组合键(如Ctrl+C)和长按键组合
- 输入冲突检测:检测是否有多个行为绑定到同一个按键
- 输入可视化:在游戏界面显示当前按键绑定
- 输入录制:支持录制玩家的输入操作并回放
- 新输入系统:使用新的InputSystem来制作输入模块
10.2 知识点代码
InputMgr.cs(输入管理器 - 最终版本)
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// 输入管理器
/// 统一管理所有键盘和鼠标输入,通过事件中心分发输入事件
/// 支持改键功能和获取任意输入信息
/// </summary>
public class InputMgr : Singleton<InputMgr>
{
#region 字段
/// <summary>
/// 输入信息字典
/// 键:事件类型
/// 值:输入信息
/// </summary>
private Dictionary<E_EventType, InputInfo> inputDic = new Dictionary<E_EventType, InputInfo>();
/// <summary>
/// 当前遍历时取出的输入信息
/// </summary>
private InputInfo nowInputInfo;
/// <summary>
/// 是否开启了输入系统检测
/// </summary>
private bool isStart;
/// <summary>
/// 用于在改键时获取输入信息的委托
/// 只有当update中获取到信息的时候 再通过委托传递给外部
/// </summary>
private UnityAction<InputInfo> getInputInfoCallBack;
/// <summary>
/// 是否开始检测输入信息
/// </summary>
private bool isBeginCheckInput = false;
#endregion
#region 构造函数
private InputMgr()
{
MonoMgr.Instance.AddUpdateListener(InputUpdate);
}
#endregion
#region 输入检测开关
/// <summary>
/// 开启或者关闭我们的输入管理模块的检测
/// </summary>
/// <param name="isStart">是否开启检测</param>
public void StartOrCloseInputMgr(bool isStart)
{
this.isStart = isStart;
}
#endregion
#region 配置按键映射
/// <summary>
/// 提供给外部改键或初始化的方法(键盘)
/// </summary>
/// <param name="eventType">事件类型</param>
/// <param name="key">按键代码</param>
/// <param name="inputType">输入类型</param>
public void ChangeKeyboardInfo(E_EventType eventType, KeyCode key, InputInfo.E_InputType inputType)
{
// 初始化
if (!inputDic.ContainsKey(eventType))
{
inputDic.Add(eventType, new InputInfo(inputType, key));
}
else // 改键
{
// 如果之前是鼠标 我们必须要修改它的按键类型
inputDic[eventType].keyOrMouse = InputInfo.E_KeyOrMouse.Key;
inputDic[eventType].key = key;
inputDic[eventType].inputType = inputType;
}
}
/// <summary>
/// 提供给外部改键或初始化的方法(鼠标)
/// </summary>
/// <param name="eventType">事件类型</param>
/// <param name="mouseID">鼠标按键ID</param>
/// <param name="inputType">输入类型</param>
public void ChangeMouseInfo(E_EventType eventType, int mouseID, InputInfo.E_InputType inputType)
{
// 初始化
if (!inputDic.ContainsKey(eventType))
{
inputDic.Add(eventType, new InputInfo(inputType, mouseID));
}
else // 改键
{
// 如果之前是鼠标 我们必须要修改它的按键类型
inputDic[eventType].keyOrMouse = InputInfo.E_KeyOrMouse.Mouse;
inputDic[eventType].mouseID = mouseID;
inputDic[eventType].inputType = inputType;
}
}
/// <summary>
/// 移除指定行为的输入监听
/// </summary>
/// <param name="eventType">事件类型</param>
public void RemoveInputInfo(E_EventType eventType)
{
if (inputDic.ContainsKey(eventType))
inputDic.Remove(eventType);
}
#endregion
#region 获取输入信息
/// <summary>
/// 获取下一次的输入信息
/// 用于改键功能中,等待玩家按下任意按键
/// </summary>
/// <param name="callBack">输入信息回调</param>
public void GetInputInfo(UnityAction<InputInfo> callBack)
{
getInputInfoCallBack = callBack;
MonoMgr.Instance.StartCoroutine(BeginCheckInput());
}
/// <summary>
/// 延迟一帧开始检测输入
/// 避免开启检测时立刻触发当前的输入
/// </summary>
/// <returns>协程迭代器</returns>
private IEnumerator BeginCheckInput()
{
// 等一帧
yield return 0;
// 一帧后才会被置成true
isBeginCheckInput = true;
}
#endregion
#region 输入检测更新
/// <summary>
/// 输入检测更新
/// </summary>
private void InputUpdate()
{
// 当委托不为空时 证明想要获取到输入的信息 传递给外部
if (isBeginCheckInput)
{
// 当一个键按下时 然后遍历所有按键信息 得到是谁被按下了
if (Input.anyKeyDown)
{
InputInfo inputInfo = null;
// 我们需要去遍历监听所有键位的按下 来得到对应输入的信息
// 键盘
Array keyCodes = Enum.GetValues(typeof(KeyCode));
foreach (KeyCode inputKey in keyCodes)
{
// 判断到底是谁被按下了 那么就可以得到对应的输入的键盘信息
if (Input.GetKeyDown(inputKey))
{
inputInfo = new InputInfo(InputInfo.E_InputType.Down, inputKey);
break;
}
}
// 鼠标
if (inputInfo == null)
{
for (int i = 0; i < 3; i++)
{
if (Input.GetMouseButtonDown(i))
{
inputInfo = new InputInfo(InputInfo.E_InputType.Down, i);
break;
}
}
}
// 把获取到的信息传递给外部
getInputInfoCallBack?.Invoke(inputInfo);
getInputInfoCallBack = null;
// 检测一次后就停止检测了
isBeginCheckInput = false;
}
}
// 如果外部没有开启检测功能 就不要检测
if (!isStart)
return;
// 遍历配置的输入映射并检测输入
foreach (E_EventType eventType in inputDic.Keys)
{
nowInputInfo = inputDic[eventType];
// 如果是键盘输入
if (nowInputInfo.keyOrMouse == InputInfo.E_KeyOrMouse.Key)
{
// 是抬起还是按下还是长按
switch (nowInputInfo.inputType)
{
case InputInfo.E_InputType.Down:
if (Input.GetKeyDown(nowInputInfo.key))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Up:
if (Input.GetKeyUp(nowInputInfo.key))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Always:
if (Input.GetKey(nowInputInfo.key))
EventCenter.Instance.EventTrigger(eventType);
break;
default:
break;
}
}
// 如果是鼠标输入
else
{
switch (nowInputInfo.inputType)
{
case InputInfo.E_InputType.Down:
if (Input.GetMouseButtonDown(nowInputInfo.mouseID))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Up:
if (Input.GetMouseButtonUp(nowInputInfo.mouseID))
EventCenter.Instance.EventTrigger(eventType);
break;
case InputInfo.E_InputType.Always:
if (Input.GetMouseButton(nowInputInfo.mouseID))
EventCenter.Instance.EventTrigger(eventType);
break;
default:
break;
}
}
}
// 始终检测Unity Axes(轴输入)
EventCenter.Instance.EventTrigger<float>(E_EventType.E_Input_Horizontal, Input.GetAxis("Horizontal"));
EventCenter.Instance.EventTrigger<float>(E_EventType.E_Input_Vertical, Input.GetAxis("Vertical"));
}
#endregion
}
InputInfo.cs(输入信息类)
using UnityEngine;
/// <summary>
/// 输入信息类
/// 用于存储和管理键盘和鼠标的输入信息
/// </summary>
public class InputInfo
{
#region 枚举
/// <summary>
/// 键盘或鼠标枚举
/// </summary>
public enum E_KeyOrMouse
{
/// <summary>
/// 键盘输入
/// </summary>
Key,
/// <summary>
/// 鼠标输入
/// </summary>
Mouse,
}
/// <summary>
/// 输入类型枚举
/// </summary>
public enum E_InputType
{
/// <summary>
/// 按下
/// </summary>
Down,
/// <summary>
/// 抬起
/// </summary>
Up,
/// <summary>
/// 长按
/// </summary>
Always,
}
#endregion
#region 字段
/// <summary>
/// 具体输入的类型——键盘还是鼠标
/// </summary>
public E_KeyOrMouse keyOrMouse;
/// <summary>
/// 输入的类型——抬起、按下、长按
/// </summary>
public E_InputType inputType;
/// <summary>
/// 键盘按键代码
/// </summary>
public KeyCode key;
/// <summary>
/// 鼠标按键ID
/// </summary>
public int mouseID;
#endregion
#region 构造函数
/// <summary>
/// 主要给键盘输入初始化
/// </summary>
/// <param name="inputType">输入类型</param>
/// <param name="key">按键代码</param>
public InputInfo(E_InputType inputType, KeyCode key)
{
this.keyOrMouse = E_KeyOrMouse.Key;
this.inputType = inputType;
this.key = key;
}
/// <summary>
/// 主要给鼠标输入初始化
/// </summary>
/// <param name="inputType">输入类型</param>
/// <param name="mouseID">鼠标按键ID</param>
public InputInfo(E_InputType inputType, int mouseID)
{
this.keyOrMouse = E_KeyOrMouse.Mouse;
this.inputType = inputType;
this.mouseID = mouseID;
}
#endregion
}
InputModuleUsageTest.cs(输入模块使用测试)
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// 输入模块使用测试
/// 演示输入模块的所有功能
/// </summary>
public class InputModuleUsageTest : MonoBehaviour
{
void Start()
{
Debug.Log("输入模块使用测试:开始");
// 开启输入检测
InputMgr.Instance.StartOrCloseInputMgr(true);
// 配置按键映射
ConfigKeyMapping();
// 监听输入事件
ListenToInputEvents();
// 延迟5秒测试改键
Invoke("TestKeyRemapping", 5f);
}
/// <summary>
/// 配置按键映射
/// </summary>
private void ConfigKeyMapping()
{
// 技能1绑定到数字键1
InputMgr.Instance.ChangeKeyboardInfo(
E_EventType.E_Input_Skill1,
KeyCode.Alpha1,
InputInfo.E_InputType.Down
);
// 技能2绑定到鼠标左键
InputMgr.Instance.ChangeMouseInfo(
E_EventType.E_Input_Skill2,
0,
InputInfo.E_InputType.Down
);
// 技能3绑定到数字键3
InputMgr.Instance.ChangeKeyboardInfo(
E_EventType.E_Input_Skill3,
KeyCode.Alpha3,
InputInfo.E_InputType.Down
);
Debug.Log("按键映射配置完成");
// 输出:按键映射配置完成
}
/// <summary>
/// 监听输入事件
/// </summary>
private void ListenToInputEvents()
{
EventCenter.Instance.AddEventListener(E_EventType.E_Input_Skill1, OnSkill1);
EventCenter.Instance.AddEventListener(E_EventType.E_Input_Skill2, OnSkill2);
EventCenter.Instance.AddEventListener(E_EventType.E_Input_Skill3, OnSkill3);
EventCenter.Instance.AddEventListener<float>(E_EventType.E_Input_Horizontal, OnHorizontalMove);
EventCenter.Instance.AddEventListener<float>(E_EventType.E_Input_Vertical, OnVerticalMove);
}
private void OnSkill1()
{
Debug.Log("技能1释放");
// 输出:技能1释放
}
private void OnSkill2()
{
Debug.Log("技能2释放(鼠标左键)");
// 输出:技能2释放(鼠标左键)
}
private void OnSkill3()
{
Debug.Log("技能3释放");
// 输出:技能3释放
}
private void OnHorizontalMove(float value)
{
if (value != 0)
{
Debug.Log($"水平移动:{value}");
// 输出:水平移动:-1 或 0.5 等
}
}
private void OnVerticalMove(float value)
{
if (value != 0)
{
Debug.Log($"垂直移动:{value}");
// 输出:垂直移动:1 或 -0.3 等
}
}
/// <summary>
/// 测试改键功能
/// </summary>
private void TestKeyRemapping()
{
Debug.Log("开始测试改键功能");
// 将技能1改为Q键
InputMgr.Instance.ChangeKeyboardInfo(
E_EventType.E_Input_Skill1,
KeyCode.Q,
InputInfo.E_InputType.Down
);
Debug.Log("技能1已改为Q键");
// 测试获取输入信息
TestGetInputInfo();
}
/// <summary>
/// 测试获取输入信息功能
/// </summary>
private void TestGetInputInfo()
{
Debug.Log("等待用户按下任意按键...");
InputMgr.Instance.GetInputInfo((inputInfo) =>
{
if (inputInfo != null)
{
if (inputInfo.keyOrMouse == InputInfo.E_KeyOrMouse.Key)
{
Debug.Log($"检测到键盘按键:{inputInfo.key}");
}
else
{
Debug.Log($"检测到鼠标按键:{inputInfo.mouseID}");
}
// 根据输入信息改键技能3
if (inputInfo.keyOrMouse == InputInfo.E_KeyOrMouse.Key)
{
InputMgr.Instance.ChangeKeyboardInfo(
E_EventType.E_Input_Skill3,
inputInfo.key,
InputInfo.E_InputType.Down
);
Debug.Log($"技能3已改键为:{inputInfo.key}");
}
}
});
}
void OnDestroy()
{
// 移除事件监听
EventCenter.Instance.RemoveEventListener(E_EventType.E_Input_Skill1, OnSkill1);
EventCenter.Instance.RemoveEventListener(E_EventType.E_Input_Skill2, OnSkill2);
EventCenter.Instance.RemoveEventListener(E_EventType.E_Input_Skill3, OnSkill3);
EventCenter.Instance.RemoveEventListener<float>(E_EventType.E_Input_Horizontal, OnHorizontalMove);
EventCenter.Instance.RemoveEventListener<float>(E_EventType.E_Input_Vertical, OnVerticalMove);
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com