懒人UI框架与操作方式识别

文章介绍了作者在GameJam2024项目中开发的简易UI框架,包括UI基类和UIManager,用于管理和控制自定义UI。此外,还讨论了如何通过Unity新输入系统识别键鼠和手柄操作方式,以及可能遇到的问题和优化建议。
摘要由CSDN通过智能技术生成

前言

        有这些奇怪需求是我在GGJ2024得出来的,就上周那场。我们队伍企图做一个吸血鬼like的游戏,并非常荣幸的收获了许多BUG和需求,于是不得不花时间整理,可能之后的好几次文章可能都要围绕那个游戏做题。我在设计游戏架构之初就打算让它同时支持键鼠和手柄游玩。但到最好赶工干着干着只能忽略了,这毕竟也是GJ。所以现在回头梳理重构一下之前的框架并记录成果。

懒人UI框架

        事先声明,这个框架是非常简易的,没有任何技术含量,存粹分享个人愚笨的记录,有需要请自行拓展。

主要部分:

1.UI基类

脚本示范:

using UnityEngine;

public class UIBase : MonoBehaviour
{
    public virtual void Open() 
    { 
        gameObject.SetActive(true); 
    }

    public virtual void Close()
    {
        gameObject.SetActive(false);
    }
}

         不难看出,所谓的UI基类其实就是我们自定义UI的模板类,也就是我们自定义的UI脚本们均继承自它,然后再把它们挂载在各个UI上。在上述代码中,UIBase仅仅包含了每个UI的开启和关闭行为,我们可以根据更具体的需求进行拓展。

        一开始我其实写成一个abstract类,但猛然发现好像每个UI在开启和关闭时都需要设置Active,索性就把它们写成虚方法以便直接继承。

2.UI管理类

脚本示范:

using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制所有UI面板
/// </summary>
public class UIManager : MonoBehaviour
{
    //有更好的单例模板可以替换
    public static UIManager Instance;

    [Serializable]
    internal class Panel
    {
        [Header("面板预制体")]
        [Tooltip("UI面板的预制体,最好是一个独立的Canvas")]
        public GameObject prefab;
        [Header("开始时打开")]
        [Tooltip("比如菜单Menu那些默认打开就好了")]
        public bool startOpen = false;
    }

    [SerializeField]
    private List<Panel> panels = new List<Panel>();
    private Dictionary<string,GameObject> name2panel = new Dictionary<string,GameObject>();
    private void Awake()
    {
        Instance = this;
        DontDestroyOnLoad(this);        

        foreach (var panel in panels)
        {
            GameObject panelIns = Instantiate(panel.prefab,transform);
            panelIns.name = panel.prefab.name;
            name2panel.Add(panelIns.name, panelIns);

            if (panel.startOpen) 
            {  
                panelIns.GetComponent<UIBase>().Open();
            }
            else
            {
                panelIns.GetComponent<UIBase>().Close();
            }
            
        }
    }

    public static void Open(string PanelName)
    {
        Instance.name2panel[PanelName].GetComponent<UIBase>().Open();
    }
    public static void Close(string PanelName)
    {
        Instance.name2panel[PanelName].GetComponent<UIBase>().Close();
    }
}

         管理类的大致思路就是以内部的Panel类为单位管理所有UI,当然此Panel非彼Panel,这里说的Panel指玩家自定义UI面板,比方说一个叫Menu的UI面板。需要做的,就是将做好的UI预制体拖入Panel的prefab中:

        当前UI Manager已经挂载在同名空物体上。

具体的代码很容易理解,这里的单例模式可以替换掉,如果已经具有单例模板。

3.UI们

脚本示范:

using UnityEngine;
using UnityEngine.EventSystems;

public class Menu : UIBase
{
    [Header("打开时选择")]
    public GameObject OpenSelected;        
       
    public void OpenPlayerPanel()
    {
        UIManager.Open("角色面板");
        Close();
    }
    public override void Close()
    {
        base.Close();
    }

    public override void Open()
    {
        base.Open();
        EventSystem.current.SetSelectedGameObject(OpenSelected);
    }
}

        可以看到,Menu作为一个自定义UI脚本,继承自UIBase,在此基础上,我们根据自己想要的具体效果实现不同的操作。不如说Menu这里的Open(),重写之后每次打开我都让当前选择的物体唯一,这样可以避免当前选择对象为空这种情况发生,在之后的操作方式识别我会细说。

        与此同时,Menu脚本应该挂载在某个UI预制体上。不过不要挂在它的子物体上,因为管理类中是通过直接获取身上的组件在找到UIbase从而实现统一管控的。这里的Menu就挂在“菜单”预制体上。

框架思路:

        好的,这是十分简单的一个框架,可能以后需要更加复杂庞大的系统的,但是这种懒人式于我而言目前已经很足够了。

        我一开始确实是想非常统一的管理所有UI,但后面发现非常不现实。比方说我想只用到管理类脚本就实现全部UI和各种功能的管理,这是非常困难的。因为每个UI要做的事都不同,所谓的统一管理我就简化为了统一控制显示。

        就像上图的OpenPlayerPanel()方法,我在预制体界面将它与某个按钮绑定之后,是在脚本中描述它的功能的:“打开角色面板”然后“关闭自己”。这里的“打开角色面板”就是通过管理类来完成。

但是每个UI自己的功能只能在自己的脚本中完成,这也是没办法的事情,如果有专门构建游戏UI的UI引擎就好了,不用像这样写一顿脚本分别挂载。事实上我写这个框架之前就是这样的,为每个UI都写一个脚本,菜单一个,状态栏一个,物品栏和商店一个。但是它们之间的相互关联确总是很麻烦,索性就让它们有所共性(UIbase)然后方便管理罢了,后续应该还会添油加醋让整个UI系统更加合理高效。

        最后梳理一下大概的使用流程:1.搭好一个UI(相当于一个Canvas);2.把它变成预制体;3.把预制体拖入某个挂载有UIManager的物体上(一般是同名空物体);4.调整参数。

        对于第4点,所谓的什么参数目前就只有要不要在一开始时打开罢了。但是我们可以自行添加修改。

操作方式识别

背景

        所谓的什么操作方式,其实就是键鼠和手柄之类的,今天也只针对键鼠和手柄两种操作方式进行识别。而且要注意,这次脚本是建立在Unity new input system上的,老的输入系统有别的方式。但是因为新输入系统太好用了,我就果断放弃老输入系统。

        再者就是新输入系统呢虽然比较麻烦但是更全面更高效,所以没学习过的建议先去学习一下,我也没有很深入研究。

        也是刚好就着这个UI系统刚好能有机会演示,所以就把它们俩个放在一起记录了。

准备

        非常简单,如果你已经熟悉新输入系统的基本操作后。那么就先新建一个Input Actions,我这里新建了一个叫test的Input Actions。这里其实可以直接复制Unity默认的那个,新建一个新输入系统下的EvenSystem,它会自带有Input System UI Input Module组件,它有个属性就是默认Input Actions,可以直接复制一份出来,改个名字接着用。因为有很多UI导航相关的map已经做好了,做起来就会很方便,UI导航还是非常重要的。

        再一个就是给某个物体加上Player Input 组件。这一步非常关键,因为我们可以从Player Input组件获取Control Scheme(CS),也就是控制方案。这个控制方案是与Input Actions相关的,如果你的Input Actions是从默认的复制过来的,那么它会自带一些调好的CS:

在左上角 ,点开就可以看到几个默认的CS,我们就是通过识别CS来间接识别操作方式的。

为了方便演示,我把Player Input挂载到了EventSystem上。

脚本:

using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;

public class Menu : UIBase
{
    [Header("打开时选择")]
    public GameObject OpenSelected;
    [Header("当前控制方案提示")]
    public TextMeshProUGUI tip;    
        
    private void FixedUpdate()
    {
        if (EventSystem.current.currentSelectedGameObject == null) EventSystem.current.SetSelectedGameObject(OpenSelected);

        if(EventSystem.current.GetComponent<PlayerInput>().currentControlScheme == "Gamepad")
        {
            tip.text = "use left stick to select...";
        }
        if (EventSystem.current.GetComponent<PlayerInput>().currentControlScheme == "Keyboard&Mouse")
        {
            tip.text = "use WASD to select...";
        }
    }
    
    public void OpenPlayerPanel()
    {
        UIManager.Open("角色面板");
        Close();
    }
    public override void Close()
    {
        base.Close();
    }

    public override void Open()
    {
        base.Open();
        EventSystem.current.SetSelectedGameObject(OpenSelected);
    }
}

        OK啊非常容易理解,就是我们在FixedUpdate中直接判断现在的CS,然后根据它切换提示。

具体CS的名字都可以改,原理就是这么个原理。然后这段代码直接加在了Menu上。

演示

        可以看到一开始我们是用键盘操作的,所以提示就变成键盘提示,后来我转到手柄操作它就自动变成手柄提示了。

意义

        相信操作方式很容易理解,但是具体的原理,特别是CS和Input Actions这一块还是要花点时间。

        至于这个东西有什么用?嘿嘿,用处就是我刚刚演示的那样:识别玩家操作方式,从而改变操作提示。经常玩游戏的都知道这是非常常见的游戏支持。我一开始做我们GGJ那个项目的时候就是想这样做一个提示,因为当时需要做一个商店页面,然后提供卖出物品这个功能,所以需要做一个按键的提示,如果玩家用的键鼠就提示玩家“鼠标右键出售”,如果是手柄就提示玩家“按B出售”;

        当时因为时间和经验的不足,没能完成这个需求,其实GJ本来也用不到,只是自己想挑战一下。

        当然还有另外一种比较节省的改提示的方式:就是把操作方式直接改在游戏设置里面,这样玩家可以更自己修改自己想要的提示。当然这两者一起作用最好。我记得法环好像就是这样的。

        当然还有其他功能需要大家的想象力了,比如根据玩家使用的操作方式改变游戏机制。这也很常见。当然基本原理也大同小异。

常见问题

        1.Player Input和Input System UI Input Module具有连带关系,可能会导致一些问题(放心,一般不会),具体可以去手册看看。但是又不得不用上Player Input组件,因为这能快速查询CS,其实虽然叫做Player,但是你想挂载在哪都行。

        2.这里虽然未提到,但是我还是想提一嘴手柄游戏操作UI的按钮导航最好设为Explicit,因为Unity的导航判断不了遮挡关系,也就是子UI上的按钮可以导航到父UI,或者反过来,这也是我在做UI时发现的小问题,可能会找个时间特地研究下优化手柄游戏的UI,这里就先提一嘴。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Moweiii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值