Unity复刻胡闹厨房复盘 模块一 新输入系统订阅链与重绑定
本文仅作学习交流,不做任何商业用途
郑重感谢siki老师的汉化教程与代码猴的免费教程以及搬运烤肉的小伙伴
版本:Unity6
模板:3D 核心
渲染管线:URP
------------------------------------------------------------------------------------------------------------------------------
在此之前你应该对新输入系统有一定的了解,关于新输入系统的用法与解释:Unity新输入系统 之 InputAction(输入配置文件最基本的单位)_unity inputaction-CSDN博客
关于新输入系统的简单实战:Unity 新输入系统实战 控制2D角色移动动画(俯视)-CSDN博客
目录
实现功能与逻辑拆解
1.获取WASD的基础输入编辑
2.自定义操作按键
3.按键重绑
4 .重绑定后的保存与读取
全局概览
我将输入管理类命名为GameInput 其创建时候就写为了单例模式,因为但凡是拥有全局唯一实例的类 或者 是该类的生命周期占据了整个场景 就应该写为单例模式
实现功能与逻辑拆解
1.获取WASD的基础输入
对于新输入系统的创建与生成C#文件我便不再赘述,Unity6自带
你可以在InputAction看到其已经定义好了很多内容
首先声明输入系统的C#类 然后开启
action = new InputSystem_Actions();action.Enable();
直接读取输入值 可以看到Move这一Action是Value的动作类型以及Vector2的控制类型
因此代码就可以直接这么写:
public Vector3 GetInputKeyboard() {Vector2 direcation = action.Player.Move.ReadValue<Vector2>();//float x = Input.GetAxisRaw("Horizontal");//float z = Input.GetAxisRaw("Vertical");Vector3 move = new Vector3(direcation.x, 0, direcation.y);move = move.normalized;return move;}
至于为什么返回单位化后的向量可以看这一篇,其并不是本文的重点 :Unity中的数学应用 之 角色移动中单位化向量的妙用 (小学难度)-CSDN博客
2.自定义操作按键

这里有个前置知识点:发布者-订阅模式的特点就是发布者发布事件与调用 ,订阅者只需要订阅上就完事了
对于此部分可以自定义Action为Button类型
对于具体按键可以勾选分类:
代码是这么写的:
private static GameInput instance;public static GameInput Instance => instance;private InputSystem_Actions action;public EventHandler interact;public EventHandler operater;public EventHandler pause;private void Awake() {if (instance == null) {instance = this;}else {Destroy(instance);}action = new InputSystem_Actions();if (PlayerPrefs.HasKey(PLAYERBDINGINFO))action.LoadBindingOverridesFromJson(PlayerPrefs.GetString(PLAYERBDINGINFO));action.Enable();//触发订阅—Eaction.Player.Interact.started += Interact_started;//触发订阅-Faction.Player.Operater.started += Operater_started;//触发订阅-ESCaction.Player.Pause.started += Pause_started;}private void OnDestroy() {action.Player.Interact.started -= Interact_started;action.Player.Operater.started -= Operater_started;action.Player.Pause.started -= Pause_started;action.Dispose();}private void Pause_started(UnityEngine.InputSystem.InputAction.CallbackContext obj) {pause?.Invoke(this, EventArgs.Empty);}private void Operater_started(UnityEngine.InputSystem.InputAction.CallbackContext obj) {operater?.Invoke(this, EventArgs.Empty);}private void Interact_started(UnityEngine.InputSystem.InputAction.CallbackContext obj) {interact?.Invoke(this, EventArgs.Empty);}
解释:下面部分是对按键手势(Action)的订阅
action.Enable();//触发订阅—Eaction.Player.Interact.started += Interact_started;//触发订阅-Faction.Player.Operater.started += Operater_started;//触发订阅-ESCaction.Player.Pause.started += Pause_started;
订阅的谁呢?是如下三个函数 注意其参数都是自动生成的
private void Pause_started(UnityEngine.InputSystem.InputAction.CallbackContext obj) {pause?.Invoke(this, EventArgs.Empty);}private void Operater_started(UnityEngine.InputSystem.InputAction.CallbackContext obj) {operater?.Invoke(this, EventArgs.Empty);}private void Interact_started(UnityEngine.InputSystem.InputAction.CallbackContext obj) {interact?.Invoke(this, EventArgs.Empty);}
而三个函数内又包裹了一层C#提供可传参委托
其参数sender代表事件的发布者 也就是GameIput类本身
e是EventArgs类型或者派生自EventArgs的类型,通常用于传递和事件相关的信息,这里传值为EventArgs.Empty 也就是空

也就是他们三个
public EventHandler interact;public EventHandler operater;public EventHandler pause;
所以对于他们三个的调用也就嵌在了对新输入系统手势的订阅上
那么谁去订阅呢?Player 你会发现Player这里又是一层封装?但不是委托与事件
BaseCounter 是柜台基类 我们以后会讲,curCounter用于存储当前玩家获得的柜台
private BaseCounter curCounter;void Start() {//Application.targetFrameRate = 60;this.HoldPoints = transform.Find("PlayerHoldPoint");GameInput.Instance.interact += OnInterAction;GameInput.Instance.operater += OnOperaterAction;}private void OnInterAction(object sender, EventArgs s) {curCounter?.Interact(this);}private void OnOperaterAction(object sender, EventArgs e) {curCounter?.InteractOperater(this);}
Interact与InteractOperater的定义在柜台类基类之中是两个虚方法,由子类去实现
using UnityEngine;public class BaseCounter : FoodObjcetHolder {[SerializeField] protected Transform SelectPrefab;public void SelectPrefabSecureAssign(string name) {if (SelectPrefab == null) {SelectPrefab = transform.Find(name);}}public virtual void Interact(Player player) {Debug.Log("未对父类进行重写");}public virtual void InteractOperater(Player player) {}public void CounterSelect() {SelectPrefab.gameObject?.SetActive(true);}public void CounterUnSelect() {SelectPrefab.gameObject?.SetActive(false);}}
子类实现我们不去考虑,目前对于一个按键的订阅链我们已经整理完了
由下图所示

你要说这不是脱了裤子放p吗?其实不然 其道理在于
如果只是GameInput类自我消化 只需要第一条黑线 但是GameInput类要与Player类进行交互 而事件的发布订阅解决了这个问题,所以有了第二条黑线
Player也不负责执行柜台的逻辑 所以就需要第三条黑线
3.按键重绑
这个的原理如下
1.获取对应的Action下的按键
action.Player.Move;action.Player.Interact;action.Player.Operater;action.Player.Pause;
2.通过对应的索引去得到对应值键 也就是下图

在回调函数中执行其他办法,注意最后那个Start()方法一定要开启不然没有任何反应
actionKey.PerformInteractiveRebinding(index).OnComplete((callback) => {Debug.Log("重新绑定完成");action.Player.Enable();SettingUI.Instance.UpdateUI();SettingUI.Instance.HideBindingInfo();PlayerPrefs.SetString(PLAYERBDINGINFO,action.SaveBindingOverridesAsJson());}).Start();
先别管actionKey,也别管函数块内部做了什么,重点下面这个API是重绑定的关键:
PerformInteractiveRebinding(index).OnComplete
做了一个枚举去得到所有不同的输入
public enum E_BindingKey{ w,a,s,d,e,f,esc
}
重新绑定只需要传入一个枚举值,就可以
public void ReBinding(E_BindingKey e_BindingKey){Debug.Log("进入重新绑定");action.Player.Disable(); InputAction actionKey = null;int index = -1;switch (e_BindingKey) {case E_BindingKey.w:index = 2;actionKey = action.Player.Move;break;case E_BindingKey.a:index = 6;actionKey = action.Player.Move;break;case E_BindingKey.s:index = 4;actionKey = action.Player.Move;break;case E_BindingKey.d:index = 8;actionKey = action.Player.Move;break;case E_BindingKey.e:index = 0;actionKey = action.Player.Interact;break;case E_BindingKey.f:index = 0;actionKey = action.Player.Operater;break;case E_BindingKey.esc:index = 0;actionKey = action.Player.Pause;break;default:break;}if(actionKey != null) {actionKey.PerformInteractiveRebinding(index).OnComplete((callback) => {Debug.Log("重新绑定完成");action.Player.Enable();SettingUI.Instance.UpdateUI();SettingUI.Instance.HideBindingInfo();PlayerPrefs.SetString(PLAYERBDINGINFO,action.SaveBindingOverridesAsJson());}).Start();}else{Debug.Log("actionKey为空");}//actionKey.Dispose();}
4 .重绑定后的保存与读取
重新加载会将场景所有类的内存回收,但是存在本地的可以持久化,因此无论是何种持久化方式都可以通过如下两个API去存取成json字符串
这里用PlayerPrefs演示
写入:
PlayerPrefs.SetString(PLAYERBDINGINFO,action.SaveBindingOverridesAsJson());
读取:
action.LoadBindingOverridesFromJson(PlayerPrefs.GetString(PLAYERBDINGINFO));
至于放在哪里 不用我多说了相信你对unity的生命周期并不陌生
全局概览
using System;
using UnityEngine;
using UnityEngine.InputSystem;
public enum E_BindingKey{ w,a,s,d,e,f,esc
}
public class GameInput : MonoBehaviour {private const string PLAYERBDINGINFO = "PLAYERBDINGINFO";private static GameInput instance;public static GameInput Instance => instance;private InputSystem_Actions action;public EventHandler interact;public EventHandler operater;public EventHandler pause;private void Awake() {if (instance == null) {instance = this;}else {Destroy(instance);}action = new InputSystem_Actions();if (PlayerPrefs.HasKey(PLAYERBDINGINFO))action.LoadBindingOverridesFromJson(PlayerPrefs.GetString(PLAYERBDINGINFO));action.Enable();//触发订阅—Eaction.Player.Interact.started += Interact_started;//触发订阅-Faction.Player.Operater.started += Operater_started;//触发订阅-ESCaction.Player.Pause.started += Pause_started;}private void OnDestroy() {action.Player.Interact.started -= Interact_started;action.Player.Operater.started -= Operater_started;action.Player.Pause.started -= Pause_started;action.Dispose();}private void Pause_started(UnityEngine.InputSystem.InputAction.CallbackContext obj) {pause?.Invoke(this, EventArgs.Empty);}private void Operater_started(UnityEngine.InputSystem.InputAction.CallbackContext obj) {operater?.Invoke(this, EventArgs.Empty);}private void Interact_started(UnityEngine.InputSystem.InputAction.CallbackContext obj) {interact?.Invoke(this, EventArgs.Empty);}/// <summary>/// 读取输入/// </summary>/// <returns>移动朝向</returns>public Vector3 GetInputKeyboard() {Vector2 direcation = action.Player.Move.ReadValue<Vector2>();//float x = Input.GetAxisRaw("Horizontal");//float z = Input.GetAxisRaw("Vertical");Vector3 move = new Vector3(direcation.x, 0, direcation.y);move = move.normalized;return move;}public void ReBinding(E_BindingKey e_BindingKey){Debug.Log("进入重新绑定");action.Player.Disable(); InputAction actionKey = null;int index = -1;switch (e_BindingKey) {case E_BindingKey.w:index = 2;actionKey = action.Player.Move;break;case E_BindingKey.a:index = 6;actionKey = action.Player.Move;break;case E_BindingKey.s:index = 4;actionKey = action.Player.Move;break;case E_BindingKey.d:index = 8;actionKey = action.Player.Move;break;case E_BindingKey.e:index = 0;actionKey = action.Player.Interact;break;case E_BindingKey.f:index = 0;actionKey = action.Player.Operater;break;case E_BindingKey.esc:index = 0;actionKey = action.Player.Pause;break;default:break;}if(actionKey != null) {actionKey.PerformInteractiveRebinding(index).OnComplete((callback) => {Debug.Log("重新绑定完成");action.Player.Enable();SettingUI.Instance.UpdateUI();SettingUI.Instance.HideBindingInfo();PlayerPrefs.SetString(PLAYERBDINGINFO,action.SaveBindingOverridesAsJson());}).Start();}else{Debug.Log("actionKey为空");}//actionKey.Dispose();}public string GetBindingKey(E_BindingKey e_BindingKey){switch (e_BindingKey) {case E_BindingKey.w:return action.Player.Move.bindings[2].ToDisplayString();case E_BindingKey.a:return action.Player.Move.bindings[6].ToDisplayString();case E_BindingKey.s:return action.Player.Move.bindings[4].ToDisplayString();case E_BindingKey.d:return action.Player.Move.bindings[8].ToDisplayString();case E_BindingKey.e:return action.Player.Interact.bindings[0].ToDisplayString();case E_BindingKey.f:return action.Player.Operater.bindings[0].ToDisplayString();case E_BindingKey.esc:return action.Player.Pause.bindings[0].ToDisplayString();default:return "";}}
}
相关文章:
Unity复刻胡闹厨房复盘 模块一 新输入系统订阅链与重绑定
本文仅作学习交流,不做任何商业用途 郑重感谢siki老师的汉化教程与代码猴的免费教程以及搬运烤肉的小伙伴 版本:Unity6 模板:3D 核心 渲染管线:URP ------------------------------…...
使用“NodeMCU”、“红外模块”实现空调控制
项目思路 空调遥控器之所以能够实现对空调的控制,是因为它能够向空调发射出特定的红外信号。从理论上来说,任何能够发射出这种相同红外信号的红外发射器,都可以充当空调遥控器(这也正是手机能够控制多种不同品牌空调的原因所在&a…...
2023年西南大学数学建模C题天气预报解题全过程文档及程序
2023年西南大学数学建模 C题 天气预报 原题再现: 天气现象与人类的生产生活、社会经济、军事活动等方方面面都密切相关,大到国家,小到个人,都受到极端天气的影响。2022年6月,全球陆地地区出现了自1850年代末人类有系…...
【大模型】使用DPO技术对大模型Qwen2.5进行微调
前言 定义 DPO(Direct Preference Optimization)即直接偏好优化算法,是一种在自然语言处理领域,特别是在训练语言模型时使用的优化策略。它的主要目的是使语言模型的输出更符合人类的偏好。 背景和原理 在传统的语言模型训练中&a…...
Maven 生命周期
文章目录 Maven 生命周期- Clean 生命周期- Build 生命周期- Site 生命周期 Maven 生命周期 Maven 有以下三个标准的生命周期: Clean 生命周期: clean:删除目标目录中的编译输出文件。这通常是在构建之前执行的,以确保项目从一个…...
网络不通该如何手动下载torch
如果遇到pip install torch2.5.0 下载不了的情况,大部分是网络的问题.可以考虑下载wheel文件在去安装 查看对应的cuda版本(举个例子:cuda为12.4,找到这个版本的 复制到服务器上下载): 有conda和pip下载的两种方式,二者选其一:如果没有安装anaconda,就直接使用pip的方式下载 如…...
基础电路的学习
1、戴维南定理 ①左边的图可简化为一个电阻+一个电压源。② ③电压源可相当于开路。将R2移到左边,R1和R2相当于并联。RR1//R2 Rx和Rt相等时,灵敏度最大,因此使Rt10K。 104电容是0.1uf。 三位数字的前两位数字为标称容量的有效数…...
对 MYSQL 架构的了解
MySQL 是一种广泛使用的关系型数据库管理系统,其架构主要包括以下几个关键部分: 一、连接层 客户端连接管理:MySQL 服务器可以同时处理多个客户端的连接请求。当客户端应用程序(如使用 Java、Python 等语言编写的程序)…...
C#中方法参数传值和传引用的情况
对于引用类型 - 传类类型的具体值时 此时传的是引用 - 单纯传类类型 此时传的是个test引用的副本,在方法内修改的是这个副本的指向 传string,集合同理,只要是指向新对象,就是引用副本在指向 对于值类型 - 传普通值类型 …...
获取显示器(主/副屏)友好名称(FriendlyName)
在开发涉及多显示器的应用程序时,获取显示器的友好名称(Friendly Name)是一个常见需求。本文将深入探讨GetMonitorFriendlyName 方法,了解其实现细节和工作原理。 方法签名 public static string GetMonitorFriendlyName(bool i…...
Apache Solr RCE(CVE-2017-12629)--vulhub
Apache Solr 远程命令执行漏洞(CVE-2017-12629) Apache Solr 是一个开源的搜索服务器。Solr 使用 Java 语言开发,主要基于 HTTP 和 Apache Lucene 实现。原理大致是文档通过Http利用XML加到一个搜索集合中。查询该集合也是通过 http收到一个…...
2.3 携程的hook实现及dlsym函数
背景知识:(排除static 情况) 一个进程中可以有相同的命名吗? -- 不能 两个进程之间可以有相同的命名吗?--可以 一个进程和另一个静态库可以有相同的命名吗?--不能 一个进程和另一个动态库之间可以有相同…...
机器学习之KNN算法
K-Nearest Neighbors (KNN) 是一种常见的机器学习算法,广泛应用于分类和回归问题。KNN是一种基于实例的学习方法,它利用训练数据集的实例来进行分类或回归预测。在KNN中,预测的结果依赖于距离度量函数计算出的最近邻实例的标签或值。下面我们…...
《全排列问题》
题目描述 按照字典序输出自然数 11 到 nn 所有不重复的排列,即 nn 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。 输入格式 一个整数 nn。 输出格式 由 1∼n1∼n 组成的所有不重复的数字序列,每行一个序列。 每个数字保留…...
pycharm 快捷键
PyCharm 是一款功能强大的集成开发环境(IDE),提供了丰富的快捷键来提高开发效率。以下是一些常用的 PyCharm 快捷键(基于 Windows/Linux 系统,Mac 系统可能略有不同): 通用快捷键 功能快捷键&a…...
若依微服务如何获取用户登录信息
文章目录 1、需求提出2、应用场景3、解决思路4、注意事项5、完整代码第一步:后端获取当前用户信息第二步:前端获取当前用户信息 5、运行结果6、总结 1、需求提出 在微服务架构中,获取当前用户的登录信息是开发常见的需求。无论是后端处理业务…...
RunCam WiFiLink连接手机图传测试
RunCam WiFiLink中文手册从这里下载 一、摄像头端 1.连接天线(易忘) 2.打开摄像头前面的盖子(易忘) 3.接上直流电源,红线为正,黑线为负 4.直流电源设置电压为14v,电流为3.15A, 通…...
TCP三次握手,四次挥手
三次握手 第一次握手:客户端向服务器发送一个 SYN 包,其中 SYN 标志位被设置为 1,表示客户端请求建立连接,并随机生成一个初始序列号 seqx 。此时客户端进入 SYN_SENT 状态,等待服务器的确认1.第二次握手:服…...
Mono里建立调试C#脚本运行环境
前面已经介绍了怎么样来执行一个嵌入式的脚本框架, 这个框架是mono编写的一个简单的例子。 如果不清楚,可以参考前文: https://blog.csdn.net/caimouse/article/details/144632391?spm=1001.2014.3001.5501 本文主要来介绍一下,我们的C#脚本是长得怎么样的,它大体如下…...
Linux dnf 包管理工具使用教程
简介 dnf 是基于 Red Hat Linux 发行版的下一代包管理工具,它代替 yum 提供更好的性能、更好的依赖处理和更好的模块化架构。 基础语法 dnf [options] [command] [package] 常用命令用法 更新元数据缓存 sudo dnf check-update# 检查已安装的包是否有可用的更…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...

