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

news2024/12/22 7:06:36

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

        郑重感谢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();
        //触发订阅—E
        action.Player.Interact.started += Interact_started;
        //触发订阅-F
        action.Player.Operater.started += Operater_started;
        //触发订阅-ESC
        action.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();
        //触发订阅—E
        action.Player.Interact.started += Interact_started;
        //触发订阅-F
        action.Player.Operater.started += Operater_started;
        //触发订阅-ESC
        action.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();
        //触发订阅—E
        action.Player.Interact.started += Interact_started;
        //触发订阅-F
        action.Player.Operater.started += Operater_started;
        //触发订阅-ESC
        action.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.coloradmin.cn/o/2263639.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Edge Scdn防御网站怎么样?

酷盾安全Edge Scdn&#xff0c;即边缘式高防御内容分发网络&#xff0c;主要是通过分布在不同地理位置的多个节点&#xff0c;使用户能够更快地访问网站内容。同时&#xff0c;Edge Scdn通过先进的技术手段&#xff0c;提高了网上内容传输的安全性&#xff0c;防止各种网络攻击…

开源数字人系统源码短视频文案提取文案改写去水印小程序

应用场景 短视频去水印&#xff1a; 个人用户&#xff1a;在社交媒体上分享短视频时&#xff0c;去除原视频中的水印&#xff0c;以保护个人隐私或避免侵权问题。企业用户&#xff1a;在广告、宣传和营销活动中&#xff0c;使用无水印的短视频以提高品牌知名度和吸引力。 文案提…

Everything实现,快速搜索文件

最近编写NTFS文件实时搜索工具, 类似 Everything 这样, 翻阅了很多博客, 结果大致如下: 1.分析比较肤浅, 采用USN日志枚举来获取文件记录 速度一言难尽, 因为日志枚举的是全盘所有文件的所有日志, 记录比文件记录还多, 速度当然很慢, 还有的甚至于是 使用 DeviceIoControl 函数…

Linux环境下使用tomcat+nginx部署若依项目

Linux Tomcat MySQL Java 是构建动态网站系统的完美解决方案之一&#xff0c;具有免费、高 效、扩展性强且资源消耗低等优良特性。 Java Web 凭借其优秀的开发框架和良好的生态被广 泛应用于社会各行业的信息化系统构建。 本实验以若依管理系统&#xff08; http://ruo…

.NET重点

B/S C/S什么语言 B/S&#xff1a; 浏览器端&#xff1a;JavaScript&#xff0c;HTML&#xff0c;CSS 服务器端&#xff1a;ASP&#xff08;.NET&#xff09;PHP/JSP 优势&#xff1a;维护方便&#xff0c;易于升级和扩展 劣势&#xff1a;服务器负担沉重 C/S java/.NET/…

前端HTTP协议传输以及背后的原理总结

一、HTTP在前端的地位 HTTP 是一种用作获取诸如 HTML 文档这类资源的协议。它是 Web 上进行任何数据交换的基础&#xff0c;同时&#xff0c;也是一种客户端—服务器&#xff08;client-server&#xff09;协议&#xff0c;也就是说&#xff0c;请求是由接受方——通常是…

城市应急指挥系统

城市应急指挥系统的重要性 随着现代化城市的高速发展&#xff0c;我们面临着多种应急突发情景&#xff0c;如自然灾害、事故灾难、公共卫生事件以及社会安全事件等。这些事件对城市的安全稳定构成严重威胁&#xff0c;因此&#xff0c;建立一套高效、全面的城市应急指挥系统显…

【软考高级】系统架构设计师复习笔记-精华版

文章目录 前言0 系统架构设计师0.1 考架构还是考系分0.2 架构核心知识0.3 架构教材变化 1 计算机操作系统1.1 cpu 组成1.2 内核的五大功能1.3 流水线技术1.4 段页式存储1.5 I/O 软件1.6 文件管理1.7 系统工程相关 2 嵌入式2.1 嵌入式技术2.2 板级支持包&#xff08;BSP&#xf…

NSDT 3DConvert:高效实现大模型文件在线预览与转换

NSDT 3DConvert 作为一个 WebGL 展示平台&#xff0c;能够实现多种模型格式免费在线预览&#xff0c;并支持大于1GB的OBJ、STL、GLTF、点云等模型进行在线查看与交互&#xff0c;这在3D模型展示领域是一个相当强大的功能。 平台特点 多格式支持 NSDT 3DConvert兼容多种3D模型…

STL 剖析

STL 六大组件 「STL 六大组件的交互关系」 Container 通过 Allocator 取得数据储存空间Algorithm 通过 Iterator 存取 Container 内容Functor 可以协助 Algorithm 完成不同的策略变化Adapter 可以修饰或套接 Functor、Iterator 配置器(allocator) 配置器&#xff1a;负责空间…

买卖股票的最佳时机 IV - 困难

************* C topic&#xff1a;188. 买卖股票的最佳时机 IV - 力扣&#xff08;LeetCode&#xff09; ************* Stock angin: Still stocks. Intuitively, it feels hard. For once: class Solution { public:int maxProfit(vector<int>& prices) {in…

#{ }和${ } 、参数处理

目录 #{ }和${ } 参数处理 入参-parameterType 使用Map 使用Bean / List<Bean> 使用多参数 Param注解&#xff08;命名参数&#xff09; 出参-resultType 返回Map / List<Map> 返回Map<String,Map> 属性名不一致-resultMap结果映射 #{ }和${ }…

机器学习探索之旅:开启智能预测的新篇章!!! 笔记 ! ! !)

目录 一 . 机器学习基础&#xff1a; 1. 什么是机器学习&#xff1a; Langley&#xff08;1996&#xff09;的定义&#xff1a; Tom Mitchell&#xff08;1997&#xff09;的定义&#xff1a; 冷雨泉&#xff08;等&#xff09;的观点&#xff1a; 2. 机器学习与人工智能…

【RAII | 设计模式】C++智能指针,内存管理与设计模式

前言 nav2系列教材&#xff0c;yolov11部署,系统迁移教程我会放到年后一起更新&#xff0c;最近年末手头事情多&#xff0c;还请大家多多谅解。 上一节我们讲述了C移动语义相关的知识&#xff0c;本期我们来看看C中常用的几种智能指针&#xff0c;并看看他们在设计模式中的运…

基于单片机的病房呼叫系统设计

摘 要&#xff1a; 文章基于 51 系列的单片机设计的病房呼叫系统 。 在以 AT89C51 单片机为核心&#xff0c;以 74HC573 锁存器 、数码管显示模块、 矩阵按键模块等为辅组成的&#xff0c;按键分布在各个病床的床头&#xff0c;可以节约接口资源&#xff0c;当按下按键&a…

编译原理复习---目标代码生成

适用于电子科技大学编译原理期末考试复习。 1. 目标代码 是目标机器的汇编代码或机器码&#xff0c;在本课程中指的是类似于汇编代码的一种形式&#xff0c;由一条条的指令构成目标代码。 抽象机指令格式&#xff1a;OP 目的操作数&#xff0c;源操作数。 我们要做的&…

Redis数据对象

基本结构图 key和value指向的是redisObject对象 type&#xff1a;标识该对象用的是什么类型&#xff08;String、List Redis数据结构 SDS SDS有4个属性&#xff1a; len&#xff1a;记录了字符串长度&#xff0c;因此获取字符串长度的时候时间复杂度O&#xff08;1&#xff…

Gale-Shapley算法

一. 设计目的 盖尔-沙普利算法&#xff08;Gale-Shapley算法&#xff09;的设计目的是为了解决稳定匹配问题&#xff0c;即在给定一组男性和女性的偏好列表的情况下&#xff0c;找到一个稳定的匹配。这里的“稳定”指的是不存在任何一对男性和女性&#xff0c;他们彼此都比当前…

JWT令牌与微服务

1. 什么是JWT JWT&#xff08;JSON Web Token&#xff09;是一种开放标准(RFC 7519)&#xff0c;它定义了一种紧凑且自包含的方式&#xff0c;用于作为JSON对象在各方之间安全地传输信息。JWT通常用于身份验证和信息交换。 以下是JWT的一些关键特性&#xff1a; 紧凑&#xff…

视频点播系统|Java|SSM|VUE| 前后端分离

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、VUE、jquery,html 5⃣️数据库可…