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

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

郑重感谢siki老师的汉化教程与代码猴的免费教程以及搬运烤肉的小伙伴

版本:Unity6

模板:3D 核心

渲染管线:URP


在此之前你应该对新输入系统有一定的了解,关于新输入系统的用法与解释:Unity新输入系统 之 InputAction(输入配置文件最基本的单位)_unity inputaction-CSDN博客

关于新输入系统的简单实战:Unity 新输入系统实战 控制2D角色移动动画(俯视)-CSDN博客

目录

实现功能与逻辑拆解

1.获取WASD的基础输入​编辑

2.自定义操作按键

3.按键重绑

[4 .重绑定后的保存与读取](#4 .重绑定后的保存与读取)

全局概览


我将输入管理类命名为GameInput 其创建时候就写为了单例模式,因为但凡是拥有全局唯一实例的类 或者 是该类的生命周期占据了整个场景 就应该写为单例模式

实现功能与逻辑拆解

1.获取WASD的基础输入

对于新输入系统的创建与生成C#文件我便不再赘述,Unity6自带

你可以在InputAction看到其已经定义好了很多内容

首先声明输入系统的C#类 然后开启

cs 复制代码
action = new InputSystem_Actions();
  action.Enable();

直接读取输入值 可以看到Move这一Action是Value的动作类型以及Vector2的控制类型

因此代码就可以直接这么写:

cs 复制代码
   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类型

对于具体按键可以勾选分类:

代码是这么写的:

cs 复制代码
    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)的订阅

cs 复制代码
        action.Enable();
        //触发订阅---E
        action.Player.Interact.started += Interact_started;
        //触发订阅-F
        action.Player.Operater.started += Operater_started;
        //触发订阅-ESC
        action.Player.Pause.started += Pause_started;

订阅的谁呢?是如下三个函数 注意其参数都是自动生成的

cs 复制代码
 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 也就是空

也就是他们三个

cs 复制代码
    public EventHandler interact;
    public EventHandler operater;
    public EventHandler pause;

所以对于他们三个的调用也就嵌在了对新输入系统手势的订阅上

那么谁去订阅呢?Player 你会发现Player这里又是一层封装?但不是委托与事件

BaseCounter 是柜台基类 我们以后会讲,curCounter用于存储当前玩家获得的柜台

cs 复制代码
       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的定义在柜台类基类之中是两个虚方法,由子类去实现

cs 复制代码
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下的按键

cs 复制代码
action.Player.Move;

action.Player.Interact;

action.Player.Operater;

action.Player.Pause;

2.通过对应的索引去得到对应值键 也就是下图

在回调函数中执行其他办法,注意最后那个Start()方法一定要开启不然没有任何反应

cs 复制代码
     actionKey.PerformInteractiveRebinding(index).OnComplete((callback) => {

         Debug.Log("重新绑定完成");
         action.Player.Enable();
         SettingUI.Instance.UpdateUI();
         SettingUI.Instance.HideBindingInfo();

         PlayerPrefs.SetString(PLAYERBDINGINFO,action.SaveBindingOverridesAsJson());
     }).Start();

先别管actionKey,也别管函数块内部做了什么,重点下面这个API是重绑定的关键:

cs 复制代码
PerformInteractiveRebinding(index).OnComplete

做了一个枚举去得到所有不同的输入

cs 复制代码
public enum E_BindingKey{ 
    w,
    a,
    s,
    d,
    e,
    f,
    esc
}

重新绑定只需要传入一个枚举值,就可以

cs 复制代码
  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演示

写入:

cs 复制代码
PlayerPrefs.SetString(PLAYERBDINGINFO,action.SaveBindingOverridesAsJson());

读取:

cs 复制代码
   action.LoadBindingOverridesFromJson(PlayerPrefs.GetString(PLAYERBDINGINFO));

至于放在哪里 不用我多说了相信你对unity的生命周期并不陌生

全局概览

cs 复制代码
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 "";
        }

    }
}
相关推荐
乘风破浪的咸鱼君1 小时前
java线程共享模型之管程(synchronized原理、wait-notify、park方法)
java
咖猫2 小时前
Google guava 最佳实践 学习指南之08 `BiMap`(双向映射)
java·开发语言·guava
娶个名字趴2 小时前
Redis(2)常用命令
java·数据库·redis·缓存
滿2 小时前
处理错误的两种方式:try...catch 与 then...catch
java·开发语言
Ttang232 小时前
Tomcat原理(4)——尝试手动Servlet的实现
java·开发语言·servlet·java-ee·tomcat·intellij-idea
lzz的编码时刻3 小时前
Java 8 Optional 详细使用教程-优雅解决NPE
java
计算机毕设指导63 小时前
基于Springboot林业产品推荐系统【附源码】
java·开发语言·spring boot·后端·mysql·spring·intellij-idea
CQU_JIAKE3 小时前
12.8&12.9[java exp4][debug]跨域问题原因详解
java·开发语言
南宫生4 小时前
力扣-图论-15【算法学习day.65】
java·学习·算法·leetcode·图论
洛嘚4 小时前
@FeignClient用于Nacos微服务间的接口调用
java·服务器