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# 检查已安装的包是否有可用的更…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

