Unity复刻胡闹厨房复盘 模块一 新输入系统订阅链与重绑定

news/2024/12/21 17:25:07/

        本文仅作学习交流,不做任何商业用途

        郑重感谢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 "";}}
}


http://www.ppmy.cn/news/1556977.html

相关文章

java线程共享模型之管程(synchronized原理、wait-notify、park方法)

文章目录 前言一、 变量的线程安全分析1.1 成员变量与静态变量是否线程安全&#xff1f;1.2 局部变量是否线程安全&#xff1f;1.3 局部变量线程安全分析具体举例&#xff1a;1. 局部变量引用的对象没有逃离方法作用域 &#xff1a;2. 局部变量引用的对象逃离了方法作用域 &…

C++设计模式:组合模式(公司架构案例)

组合模式是一种非常有用的设计模式&#xff0c;用于解决**“部分-整体”**问题。它允许我们用树形结构来表示对象的层次结构&#xff0c;并且让客户端可以统一地操作单个对象和组合对象。 组合模式的核心思想 什么是组合模式&#xff1f; 组合模式的目的是将对象组织成树形结…

多音轨视频使用FFmpeg删除不要音轨方法

近期给孩子找宫崎骏动画&#xff0c;但是有很多是多音轨视频但是默认的都是日语&#xff0c;电视上看没办法所以只能下载后删除音轨文件只保留中文。 方法分两步&#xff0c;先安装FFmpeg在转文件即可。 第一步FFmpeg安装 FFmpeg是一个开源项目&#xff0c;包含了处理视频的…

HTMLCSS:酷炫的3D开关控件

这段代码创建了一个具有 3D 效果的开关控件&#xff0c;当用户点击滑块时&#xff0c;滑块会移动到开关的另一侧&#xff0c;同时改变背景颜色&#xff0c;模拟开关的开启和关闭状态。动画效果增加了页面的互动性和视觉吸引力。 演示效果 HTML&CSS <!DOCTYPE html>…

React 工具和库面试题(一)

1. 如何在 React 项目中使用 Hooks 从服务端获取数据&#xff1f; 在 React 中&#xff0c;我们通常使用 useEffect Hook 来进行副作用操作&#xff0c;比如从服务端获取数据&#xff0c;结合 useState 来管理数据状态。 基本步骤&#xff1a; 使用 useEffect 来执行异步操作…

BERT模型入门(2)BERT的工作原理

文章目录 如名称所示&#xff0c;BERT&#xff08;来自Transformer的双向编码器表示&#xff09;是基于Transformer模型。我们可以将BERT视为只有编码器部分的Transformer。 在上一个主题《Transformer入门》中&#xff0c;我们了解到将句子作为输入喂给Transformer的编码器&a…

MLM: 掩码语言模型的预训练任务

MLM: 掩码语言模型的预训练任务 掩码语言模型&#xff08;Masked Language Model, MLM&#xff09;是一种用于训练语言模型的预训练任务&#xff0c;其核心目标是帮助模型理解和预测语言中的上下文关系。以下是对这一概念的详细说明&#xff1a; 基本定义&#xff1a; MLM是一…

JAVA进制转换-对不同位数的转换方法

JAVA进制转换-对不同位数的转换方法 实例结果代码补叙 实例 第一个输入参数设为被转换的数值&#xff0c;第二个输入参数设为源来的位数&#xff0c;第三个输入参数设为目标的位数。 /*** 位数转换* args[0] 被转换值* args[1] 源位数* args[2] 目标位数*/public static vo…