Unity UI架构设计:界面开关逻辑解析

效果预览

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_实例化

基础面板所需要的代码

UIType UI的基础信息

知识介绍:
c# 属性: public int Id { get; private set; }
说明Id这个属性在其所在的类外被调用时,只能获取它的值而不能设置它的值

用来存放预制体中该UI的名字以及路径,基础面板含有这个对象。

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIType 
{
    public string Name { get; private set; }
    /// <summary>
    /// UI路径
    /// </summary>
    public string Path { get; private set; }
    public UIType(string path)
    {
        Path = path;
        Name = path.Substring(path.LastIndexOf('/') + 1);
    }
}

这些文件的路径随后需要通过代码进行获取:
unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_System_02

BasePanel 基础面板类

它是所有面板类的基类,当需要新的面板时,首先需要继承该类。
它需要一个UIType,记录它需要的预制体的路径及名字

然后,对于一个面板来说,存在四种状态

  1. 处于操作当前面板的状态(此时需要调用onEnter函数,因此下面这样书写)
  2. 暂停面板状态,比如说此时弹出了一个新的子面板,此时不能对原来的面板进行操作(OnPause)
  3. 恢复面板状态,比如说从其他面板回到这个面板时,此时可以重新操作,于是需要结束暂停面板时的操作
  4. 退出面板状态,退出当前面板时,需要执行一些操作
    因此需要这四个函数
  5. 登录后复制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class BasePanel
{
    public UIType UIType { get; private set; }
    public BasePanel(UIType uIType)
    {
        UIType = uIType;
    }
    public virtual void OnEnter() { }

    public virtual void OnPause() { }
    /// <summary>
    /// UI继续时的操作
    /// </summary>
    public virtual void OnResume() { }

    public virtual void OnExit() { }


}

例如当从开始界面调出设置界面的时候,此时无法再对开始界面的按钮进行操作,因此此时也就是OnPause状态

UIManager UI管理器

每一个面板下面可能有很多个UI属性,因此每个面板都需要一个UI管理器,它有获取面板下的UI,创建UI,或者销毁UI的功能。

首先需要掌握Instantiate函数的用法,因为我们接下来需要生成预制体。

Instantiate函数是unity3d中进行实例化的函数,也就是对一个对象进行复制操作的函数,这个函数共有五个重载(overloaded)函数,对这五个函数的理解不清楚的话产生的效果也不相同,现在对这五个函数做一定的理解。

先附上unity3d API 中对这个函数的描述:

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_UI_03


Instantiate函数实例化是将original对象的所有子物体和子组件完全复制,成为一个新的对象。这个新的对象拥有与源对象完全一样的东西,包括坐标值等。

original:用来做复制操作的对像物体,源对象

position:实例化的对象的位置坐标

rotation:实例化的对象的旋转坐标(旋转四元数)

parent:实例化对象的父对象,就是给这个实例化对象找的爹,在完成实例化函数处理后,实例化对象将在父对象下,是父对象的子对象

instantiateWorldSpace(老的叫WorldSpaceStays):这个值为TRUE,表示实例化对象相对于世界坐标系的位置(是位置,不是坐标值,比如实例化前在Canvas左上角,实例化后还在左上角)不变,相对于父对象的坐标值变了。为false,表示实例化对象相对于父对象的坐标值不变,但在世界坐标系中的位置变了。

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_UI_04


这两个函数的区别是instantiateWorldSpace等于true/false的区别。

第一个函数相当于instantiateWorldSpace等于false。效果:实例化对象将放在父对象下,完全保存了源对象的属性,其位置坐标值是相对于父对象的,值不变,在世界坐标系下位置发生变化。

第二个函数相当于instantiateWorldSpace等于TRUE。效果:实例化对象将放在父对象下,完全保存了源对象的属性,其位置坐标值是相对于父对象的,值改变,在世界坐标系下位置不发生变化。

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 存储所有UI信息,并可以创建或者销毁UI
/// </summary>
public class UIManager
{
    // Start is called before the first frame update
    /// <summary>
    /// 存储所有UI信息的字典,每一个UI信息都会对应一个GameObject
    /// </summary>
    private Dictionary<UIType, GameObject> dicUI;

    public UIManager()
    {
        dicUI = new Dictionary<UIType, GameObject>();
    }

    /// <summary>
    /// 获取一个UI对象
    /// </summary>
    /// <param name="type">UI信息</param>
    /// <returns></returns>
    public GameObject GetSingleUI(UIType type)
    {
        GameObject parent = GameObject.Find("Canvas");
        if (!parent)
        {
            Debug.LogError("Canvas不存在,请仔细查找有无这个对象");
            return null;
        }
        if (dicUI.ContainsKey(type)) return dicUI[type];
        GameObject ui = GameObject.Instantiate(Resources.Load<GameObject>(type.Path), parent.transform);//此处是在Canvas下生成预制体,函数使用方法见上面解析
        ui.name = type.Name;
        return ui;
    }

    /// <summary>
    /// 销毁一个UI对象
    /// </summary>
    /// <param name="type">UI信息</param>
    public void DestroyUI(UIType type)
    {
        if (dicUI.ContainsKey(type))
        {
            DestroyUI(type);
            dicUI.Remove(type);
        }
    }

}

PanelManager面板管理器

对于一个面板来说,我们可能会需要通过点击这个面板下的按钮然后开启新的面板,当我们开启一个新的面板时,我们可能无法操作原来的那个面板,因此此时就需要让原来的那个面板执行暂停功能。
而当我们连续开启多个面板时,然后再一个个关闭,为了保证顺序,那么此时需要用到栈的结构
当新的面板出现时,需要暂停原来的面板。
关闭面板时,需要执行该面板的Exit工作,然后将其Pop出来,当Pop面板后,如果当前还有面板,我们需要将该面板由暂停功能恢复到正常状态的功能,因此执行OnResume函数。

PanelManager还有一个功能就是调用UIManager来生成预制体,采用这个函数:
uiManager.GetSingleUI(nextPanel.UIType);每次Push的时候都要调用这个函数,这样才能生成预制体。

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 面板管理器 用栈来存储UI
/// </summary>
public class PanelManager 
{
    /// <summary>
    /// 存储UI面板的栈
    /// </summary>
    private Stack<BasePanel> stackPanel;
    /// <summary>
    /// UI管理器
    /// </summary>
    private UIManager uiManager;
    private BasePanel panel;

    public PanelManager()
    {
        stackPanel = new Stack<BasePanel>();
        uiManager = new UIManager();
    }
    /// <summary>
    /// UI的入栈操作,此操作会显示一个面板
    /// </summary>
    /// <param name="nextPanel">要显示的面板</param>
    public void Push(BasePanel nextPanel)
    {
        if (stackPanel.Count > 0)//如果当前没有面板,则不需要执行暂停面板的操作
        {
            panel = stackPanel.Peek();//获取栈顶
            panel.OnPause();
        }
        stackPanel.Push(nextPanel);
        GameObject panelGo = uiManager.GetSingleUI(nextPanel.UIType);
    }

    /// <summary>
    /// 执行面板的出栈操作,此操作会执行面版的OnExit方法
    /// </summary>
    public void Pop()
    {
        if (stackPanel.Count > 0)
        {
            stackPanel.Peek().OnExit();
            stackPanel.Pop();
        }
        if (stackPanel.Count > 0) stackPanel.Peek().OnResume();
    }
}

StartPanel 创建一个面板实例脚本

接下来可以创建实例面板的脚本了。
StartPanel:
它需要带有预制体的路径,然后用路径创建一个UIType,然后用UIType来作为基类构造函数的参数。

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 开始主面板
/// </summary>
public class StartPanel :BasePanel
{
    static readonly string path = "Prefabs/UI/Panel/StartPanel";
    public StartPanel():base(new UIType(path)) { }//用预制体的路径创建一个UIType,并且将这个UIType作为参数,然后去调用基类的构造函数。
}

有了上面那个脚本之后,我们只需要在

StartManager 生成面板

StartManager:
创建一个StartManager脚本,并且生成上面的那个StartPanel,并将其Push放入PanelManager即可产生StartPanel面板

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StartManager : MonoBehaviour
{
    PanelManager panelManager;
    // Start is called before the first frame update
    private void Awake()
    {
        panelManager = new PanelManager();
    }
    void Start()
    {
        panelManager.Push(new StartPanel());
    }
    // Update is called once per frame

}

场景跳转

接下来实现场景间的跳转

SceneState

首先书写一个SceneState,作为抽象基类,包含有进入场景和退出场景操作的方法

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class SceneState
{
    /// <summary>
    /// 场景进入时执行的操作
    /// </summary>
    public abstract void OnEnter();

    /// <summary>
    /// 场景退出时执行的操作
    /// </summary>
    public abstract void OnExit();

}

接下来书写一个开始场景,继承初始场景

StartScene和MainScene

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class StartScene :SceneState
{

    /// <summary>
    /// 场景名称
    /// </summary>
    readonly string sceneName = "Start";
    PanelManager panelManager;
    public override void OnEnter()
    {
        panelManager = new PanelManager();
        if (SceneManager.GetActiveScene().name != sceneName)
        {
            SceneManager.LoadScene(sceneName);
            SceneManager.sceneLoaded += SceneLoaded;//添加方法的绑定
        }
        else
        {
            panelManager.Push(new StartPanel());
        }
    }
    public override void OnExit()
    {
        SceneManager.sceneLoaded -= SceneLoaded;
    }

    /// <summary>
    /// 场景加载完毕后执行的方法
    /// </summary>
    /// <param name="scene"></param>
    /// <param name="load"></param>
    private void SceneLoaded(Scene scene, LoadSceneMode load)
    {
        panelManager.Push(new StartPanel());
        Debug.Log($"{sceneName}场景加载完毕");
    }

}

事实上,在阅读上面的代码的时候,你可能会产生了这样的问题,这是委托的用法

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_System_05


unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_实例化_06

C#委托可以查看我写的另外一篇文章

然后再仿照上面,书写一个主场景MainScene

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainScene :SceneState
{
    // Start is called before the first frame update
    /// <summary>
    /// 场景名称
    /// </summary>
    readonly string sceneName = "Main";
    PanelManager panelManager;
    public override void OnEnter()
    {
        panelManager = new PanelManager();
        if (SceneManager.GetActiveScene().name != sceneName)
        {
            SceneManager.LoadScene(sceneName);
            SceneManager.sceneLoaded += SceneLoaded;//将函数委托于sceneLoaded上。Unity的机制中,当加载场景时,会自动调用SceneLoaded。而我们只需要将函数委托于其上,就可以在加载场景时调用我们委托与其上的函数了。
        }
        else
        {
            panelManager.Push(new StartPanel());
        }
    }
    public override void OnExit()
    {
        SceneManager.sceneLoaded -= SceneLoaded;
    }

    /// <summary>
    /// 场景加载完毕后执行的方法
    /// </summary>
    /// <param name="scene"></param>
    /// <param name="load"></param>
    private void SceneLoaded(Scene scene, LoadSceneMode load)
    {
        Debug.Log($"{sceneName}场景加载完毕");
    }
}

SceneSystem 场景状态管理系统

再书写一个场景的状态管理系统,其实它只有一个函数,就是设置当前场景为所需要的场景。

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 场景的状态管理系统
/// </summary>
public class SceneSystem
{
    /// <summary>
    /// 场景状态类
    /// </summary>
    SceneState sceneState;

    /// <summary>
    /// 设置当前场景并进入当前场景
    /// </summary>
    /// <param name="state"></param>
    public void SetScene(SceneState state)
    {
        if (sceneState != null) sceneState.OnExit();
        sceneState = state;
        if (sceneState != null) sceneState.OnEnter();

        //sceneState?.OnExit();//这段书写方式和上面那段代码是相同的意义
        //sceneState = state;
        //sceneState?.OnEnter();
    }

}

再创建一个GameRoot ,然后在unity中用空物体挂接这个脚本,它用来生成场景

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
///管理全局的一些东西
/// </summary>
public class GameRoot : MonoBehaviour
{
    public static GameRoot Instance { get; private set; }
    public SceneSystem SceneSystem { get; private set; }
    private void Awake()
    {
        Instance = this;
        SceneSystem = new SceneSystem();
    }

    private void Start()
    {
        SceneSystem.SetScene(new StartScene());
    }
}

然后在StartPanel中添加Start的按键:

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_UI_07


并且在StartPanel的面板中添加跳转的按钮,然后想要按下按钮时跳转,写下以下脚本:

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_UI_08


此时,点击按钮便可以成功从Start场景跳转至Main场景了

但是我们注意到,当进入Main场景时,并没有面板被推出,于是为此,首先需要写一个MainPanel的代码:

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// Main场景的主面板
/// </summary>
public class MainPanel : BasePanel
{
    static readonly string path = "Prefabs/UI/Panel/MainPanel";
    public MainPanel() : base(new UIType(path)) { }

    public override void OnEnter()
    {

    }
}

然后修改MainScene,下面这个方法是加载完场景后调用的,我们在场景加载完后推出新面板:

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_System_09

此时按下主面板的开始建

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_实例化_10


即可跳转到Start界面并推出Panel

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_UI_11

场景间任意跳转

当从Start场景跳转到主场景时会发现,GameRoot就没了,这样就不好管理跳转回去,

为此,我们添加这样的函数:

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_实例化_12


此时即使跳转,GameRoot也不会消失

接下来实现当按下MainPanel的按键时,返回主场景的功能:

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_UI_13

于是需要在MainPanel中添加一个右上角的关闭按键

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_System_14

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_实例化_15


做到这里后即可实现按下主场景中的叉键场景跳转,但是出现了一个问题,当再次回到主场景时,多了一个GameRoot

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_UI_16


为了防止这种情况,我们可以

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_System_17

代码优化

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_实例化_18

然后在每个场景结束时,都弹出所有面板

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_System_19


同理MainScene也这样做:

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_实例化_20


unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_System_21


像这样:

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_UI_22

还有一个优化就是,我们可能会在框架以外的地方用到Push或者Pop,所以可以在这里定义一个:

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_System_23

然后就可以在Scene的脚本中:

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_实例化_24

unity UI 架构 界面打开 关闭的逻辑 unity中的ui框架_UI_25

完整代码

UIType.cs

用来存放预制体中该UI的名字以及路径,基础面板含有这个对象。

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIType 
{
    public string Name { get; private set; }
    /// <summary>
    /// UI路径
    /// </summary>
    public string Path { get; private set; }
    public UIType(string path)
    {
        Path = path;
        Name = path.Substring(path.LastIndexOf('/') + 1);
    }
}

BasePanel.cs

所有面板类的基类,当需要新的面板时,首先需要继承该类。

对于一个面板来说,存在四种状态,c

  1. 处于操作当前面板的状态(此时需要调用onEnter函数)
  2. 暂停面板状态,比如说此时弹出了一个新的子面板,此时不能对原来的面板进行操作(OnPause)
  3. 恢复面板状态,比如说从其他面板回到这个面板时,此时可以重新操作,于是需要结束暂停面板时的操作
  4. 退出面板状态,退出当前面板时,需要执行一些操作
  5. 登录后复制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class BasePanel
{
    /// <summary>
    /// UI信息
    /// </summary>
    public UIType UIType { get; private set; }
    /// <summary>
    /// UI管理工具
    /// </summary>
    public UITool UITool { get; private set; }

    public PanelManager PanelManager { get; private set; }
    public UIManager UIManager { get; private set; }
    public BasePanel(UIType uIType)
    {
        UIType = uIType;
    }

    public void Initialize(UITool tool)
    {
        UITool = tool;
    }

    /// <summary>
    /// 初始化面板管理器
    /// </summary>
    /// <param name="panelManager"></param>
    public void Initialize(PanelManager panelManager)
    {
        PanelManager = panelManager;
    }

    public void Initialize(UIManager uiManager)
    {
        UIManager = uiManager;
    }

    public virtual void OnEnter() { }

    public virtual void OnPause() {
        UITool.GetOrAddComponent<CanvasGroup>().blocksRaycasts = false;
    }
    /// <summary>
    /// UI继续时的操作
    /// </summary>
    public virtual void OnResume() {
        UITool.GetOrAddComponent<CanvasGroup>().blocksRaycasts = true;
    }

    public virtual void OnExit() {
        UIManager.DestroyUI(UIType);
    }

    public void Push(BasePanel panel) => PanelManager?.Push(panel);
    //注意,上行这种写法和下行一样:
    /*public void Push(BasePanel panel)
    {
        PanelManager?.Push(panel);
    }*/

    public void Pop() => PanelManager?.Pop();
}

UIManager.cs

每一个面板下面可能有很多个UI属性,因此每个面板都需要一个UI管理器,它有获取面板下的UI,创建UI,或者销毁UI的功能

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 存储所有UI信息,并可以创建或者销毁UI
/// </summary>
public class UIManager
{
    // Start is called before the first frame update
    /// <summary>
    /// 存储所有UI信息的字典,每一个UI信息都会对应一个GameObject
    /// </summary>
    private Dictionary<UIType, GameObject> dicUI;

    public UIManager()
    {
        dicUI = new Dictionary<UIType, GameObject>();
    }

    /// <summary>
    /// 获取当前面板下的某个UI对象,如果没有该UI对象,我们则创建一个
    /// </summary>
    /// <param name="type">UI信息</param>
    /// <returns></returns>
    public GameObject GetSingleUI(UIType type)
    {
    	//在每个面板中,其UI信息都存放在Canva(画布下),画布和画布中的所有信息就组成了一个面板,因此首先需要找到这个画布
        GameObject parent = GameObject.Find("Canvas");//在游戏中查找名为Canvas的对象
        if (!parent)
        {
            Debug.LogError("Canvas不存在,请仔细查找有无这个对象");
            return null;
        }
        //如果已经存在这个对象则直接返回该对象,如果不存在则生成一个对象
        if (dicUI.ContainsKey(type)) return dicUI[type];
        GameObject ui = GameObject.Instantiate(Resources.Load<GameObject>(type.Path), parent.transform);
        ui.name = type.Name;
        dicUI.Add(type, ui);
        return ui;
    }

    /// <summary>
    /// 销毁一个UI对象
    /// </summary>
    /// <param name="type">UI信息</param>
    public void DestroyUI(UIType type)
    {
        if (dicUI.ContainsKey(type))
        {
            GameObject.Destroy(dicUI[type]);
            dicUI.Remove(type);
        }
    }

}

PanelManager.cs

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 面板管理器 用栈来存储UI
/// </summary>
public class PanelManager 
{
    /// <summary>
    /// 存储UI面板的栈
    /// </summary>
    private Stack<BasePanel> stackPanel;
    /// <summary>
    /// UI管理器
    /// </summary>
    private UIManager uiManager;
    private BasePanel panel;

    public PanelManager()
    {
        stackPanel = new Stack<BasePanel>();
        uiManager = new UIManager();
    }
    /// <summary>
    /// UI的入栈操作,此操作会显示一个面板
    /// </summary>
    /// <param name="nextPanel">要显示的面板</param>
    public void Push(BasePanel nextPanel)
    {
        if (stackPanel.Count > 0)
        {
            panel = stackPanel.Peek();//获取栈顶
            panel.OnPause();

        }
        stackPanel.Push(nextPanel);
        GameObject panelGo = uiManager.GetSingleUI(nextPanel.UIType);
        nextPanel.Initialize(new UITool(panelGo));
        nextPanel.Initialize(this);
        nextPanel.Initialize(uiManager);
        nextPanel.OnEnter();
        
    }

    /// <summary>
    /// 执行面板的出栈操作,此操作会执行面版的OnExit方法
    /// </summary>
    public void Pop()
    {
        if (stackPanel.Count > 0)
        {
            stackPanel.Pop().OnExit();
            //把这个面板给弹出,↓如果这个面板还有的话就继续
        }
        if (stackPanel.Count > 0) stackPanel.Peek().OnResume();

    }
        
    public void PopAll()
    {
        while (stackPanel.Count > 0) stackPanel.Pop().OnExit();
    }
}

StartPanel.cs

上面就是创建一个面板所需要的脚本,接下来我们创建一个实例面板脚本,它需要预制体的路径。
并且我们需要对这个面板进行操作,因此需要重载OnEnter函数。

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 开始主面板
/// </summary>
public class StartPanel :BasePanel
{
    static readonly string path = "Prefabs/UI/Panel/StartPanel";
    public StartPanel():base(new UIType(path)) { }

    public override void OnEnter()
    {
        //base.OnEnter();
        UITool.GetOrAddComponentInChildren<Button>("Setting").onClick.AddListener(() =>
        {
            //点击事件可以写在这里面
            Debug.Log("点击了设置按钮");
            //PanelManager.Push(new SettingPanel());
            Push(new SettingPanel());
        });

        UITool.GetOrAddComponentInChildren<Button>("Play").onClick.AddListener(() =>
        {
            //点击事件可以写在这里面
            //Debug.Log("点击了设置按钮");
            GameRoot.Instance.SceneSystem.SetScene(new MainScene());
        });
    }


}

UITool.cs

在面板里,我们有时需要获取/添加这个面板下的一些组件,或者是获取/添加其子对象的组件,我们可以将其放在BasePanel中实现。
但是我们可以书写一个工具类UITool,将这些函数放在UITool类中实现,然后让BasePanel有UITool类这个子对象,然后只需要让BasePanel调用UITool类中的找组件函数即可实现。

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UITool
{
    /// <summary>
    /// 当前的活动面板
    /// </summary>
    GameObject activePanel;
    public UITool(GameObject panel)
    {
        activePanel = panel;
    }

    /// <summary>
    /// 给当前的活动面板获取或添加一个组件
    /// </summary>
    /// <typeparam name="T">组件类型</typeparam>
    /// <returns>组件</returns>
    public T GetOrAddComponent<T>() where T : Component
    {
        if (activePanel.GetComponent<T>() == null)
        {
            activePanel.AddComponent<T>();
        }
        return activePanel.GetComponent<T>();
    }

    /// <summary>
    /// 根据名称查找子对象
    /// </summary>
    /// <param name="name">子对象名称</param>
    /// <returns></returns>
    public GameObject FindChildGameObject(string name)
    {
        Transform[] trans = activePanel.GetComponentsInChildren<Transform>();

        foreach (Transform item in trans)
        {
            if (item.name == name)
            {
                return item.gameObject;
            }
        }
        Debug.LogWarning($"{activePanel.name}里找不到名为{name}的子对象");
        return null;
    }

    /// <summary>
    /// 根据名称获取一个子对象的组件
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    public T GetOrAddComponentInChildren<T>(string name) where T : Component
    {
        GameObject child = FindChildGameObject(name);
        if (child)
        {
            if (child.GetComponent<T>() == null) child.AddComponent<T>();
            return child.GetComponent<T>();
        }
        else return null;
    }

}

SettingPanel.cs

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SettingPanel : BasePanel
{
    static readonly string path = "Prefabs/UI/Panel/SettingPanel";
    public SettingPanel() : base(new UIType(path)) { }

    public override void OnEnter()
    {
        //base.OnEnter();
        UITool.GetOrAddComponentInChildren<Button>("Setting2").onClick.AddListener(() =>
        {
            //点击事件可以写在这里面
            Debug.Log("点击了设置2按钮");
            PanelManager.Push(new SettingPanel2());
        });

        UITool.GetOrAddComponentInChildren<Button>("Exit").onClick.AddListener(() =>
        {
            //点击事件可以写在这里面
            Debug.Log("点击了退出设置按钮");
            PanelManager.Pop();
        });

    }
}

StartManager.cs

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StartManager : MonoBehaviour
{
    PanelManager panelManager;
    // Start is called before the first frame update
    private void Awake()
    {
        panelManager = new PanelManager();
    }
    void Start()
    {
        panelManager.Push(new StartPanel());
    }
    // Update is called once per frame

}

/ <summary>
/ 
/ </summary>
//public class UITool
//{
//    /// <summary>
//    /// 当前的活动面板
//    /// </summary>
//    GameObject activePanel;
//    public UITool(GameObject panel)
//    {
//        activePanel = panel;
//    }

//    /// <summary>
//    /// 给当前的活动面板获取或添加一个组件
//    /// </summary>
//    /// <typeparam name="T">组件类型</typeparam>
//    /// <returns>组件</returns>
//    public T GetOrAddComponent<T>() where T : Component
//    {
//        if (activePanel.GetComponent<T>() == null)
//        {
//            activePanel.AddComponent<T>();
//        }
//        return activePanel.GetComponent<T>();
//    }

//    /// <summary>
//    /// 根据名称查找子对象
//    /// </summary>
//    /// <param name="name">子对象名称</param>
//    /// <returns></returns>
//    public GameObject FindChildGameObject(string name)
//    {
//        Transform[] trans = activePanel.GetComponentsInChildren<Transform>();

//        foreach (Transform item in trans)
//        {
//            if (item.name == name)
//            {
//                return item.gameObject;
//            }
//        }
//        Debug.LogWarning($"{activePanel.name}里找不到名为{name}的子对象");
//        return null;
//    }
//}

/ <summary>
/ 根据名称获取一个子对象的组件
/ </summary>
/ <typeparam name="T"></typeparam>
/ <param name="name"></param>
/ <returns></returns>
//public T GetOrAddComponentInChildren<T>(string name) where T : Component
//{
//    GameObject child = FindChildGameObject(name);
//    if (child)
//    {
//        if (child.GetComponent<T>() == null) child.AddComponent<T>();
//        return child.GetComponent<T>();
//    }
//    else return null;
//}
/
/
SettingPanel(此处复制粘贴StartPanel
//using UnityEngine.UI;
//public class SettingPanel : BasePanel
//{
//    static readonly string path = "Prefabs/UI/Panel/SettingPanel";
//    public SettingPanel() : base(new UIType(path)) { }

//    public override void OnEnter()
//    {
//        //base.OnEnter();
//        //UITool.GetOrAddComponentInChildren<Button>("BtnSetting").onClick.AddListener(() =>
//        //{
//        //点击事件可以写在这里面
//        //    Debug.Log("点击了设置按钮");
//        //   PanelManager.Push(new SettingPanel());
//        //})

//        UITool.GetOrAddComponentInChildren<Button>("BtnExit").onClick.AddListener(() =>
//        {
//            PanelManager.Pop();
//        });

//    }
//    public override void OnExit()
//    {
//        //base.OnExit();
//        UIManager.DestroyUI(UIType);
//    }

//    /// <summary>
//    /// 下面这两个函数是为了在点开设置面板时,封面的按钮设置应该关掉,所以此时应该将下面这两个函数放在StartPanel,或者可以直接放在BasePanel里面
//    /// </summary>
//    public override void OnPause()
//    {
//        UITool.GetOrAddComponent<CanvasGroup>().blocksRaycasts = false;
//    }

//    public override void OnResume()
//    {
//        UITool.GetOrAddComponent<CanvasGroup>().blocksRaycasts = false; 
//    }
//}

/接下来需要 减少设置面板

/SceneState.cs
/

// ///场景状态
//public abstract class SceneState
//{
//    /// <summary>
//    /// 场景进入时执行的操作
//    /// </summary>
//    public abstract void OnEnter();

//    /// <summary>
//    /// 场景退出时执行的操作
//    /// </summary>
//    public abstract void OnExit();
//}



/开始场景
/using UnityEngine.SceneManagement
//public class StartScene : SceneState
//{
//    /// <summary>
//    /// 场景名称
//    /// </summary>
//    readonly string sceneName = "Start";
//    PanelManager panelManager;
//    public override void OnEnter()
//    {
//        panelManager = new PanelManager();
//        if (SceneManager.GetActiveScene().name != sceneName)
//        {
//            SceneManager.LoadScene(sceneName);
//            SceneManager.sceneLoaded += SceneLoaded;//添加方法的绑定
//        }
//        else
//        {
//            panelManager.Push(new StartPanel());
//        }
//    }
//    public override void OnExit()
//    {
//        SceneManager.sceneLoaded -= SceneLoaded;
//    }

//    /// <summary>
//    /// 场景加载完毕后执行的方法
//    /// </summary>
//    /// <param name="scene"></param>
//    /// <param name="load"></param>
//    private void SceneLoaded(Scene scene,LoadSceneMode load)
//    {
//        Debug.Log($"{sceneName}场景加载完毕");
//    }
//}



/MainScene 复制上面的StartScene
/


/SceneSystem.cs
/
//public class SceneSystem
//{
//    /// <summary>
//    /// 场景状态类
//    /// </summary>
//    SceneState sceneState;

//    /// <summary>
//    /// 设置当前场景并进入当前场景
//    /// </summary>
//    /// <param name="state"></param>
//    public void SetScene(SceneState state)
//    {
//        if (sceneState != null) sceneState.OnExit();
//        sceneState = state;
//        if (sceneState != null) sceneState.OnEnter();

//        sceneState?.OnExit();
//        sceneState = state;
//        sceneState?.OnEnter();
//    }

//}



GameRoot.cs
/管理全局的一些东西
//public class GameRoot : MonoBehaviour
//{
//    public static GameRoot Instance { get; private set; }
//    public SceneSystem SceneSystem { get; private set; }
//    private void Awake()
//    {
//        Instance = this;
//        SceneSystem = new SceneSystem();
//    }

//    private void Start()
//    {
//        SceneSystem.SetScene(new StartScene());
//    }
//}


/MainPanel
/main场景的主面板
/

SceneState.cs

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class SceneState
{
    /// <summary>
    /// 场景进入时执行的操作
    /// </summary>
    public abstract void OnEnter();

    /// <summary>
    /// 场景退出时执行的操作
    /// </summary>
    public abstract void OnExit();

}

SceneSystem.cs

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 场景的状态管理系统
/// </summary>
public class SceneSystem
{
    /// <summary>
    /// 场景状态类
    /// </summary>
    SceneState sceneState;

    /// <summary>
    /// 设置当前场景并进入当前场景
    /// </summary>
    /// <param name="state"></param>
    public void SetScene(SceneState state)
    {
        if (sceneState != null) sceneState.OnExit();
        sceneState = state;
        if (sceneState != null) sceneState.OnEnter();

        //sceneState?.OnExit();
        //sceneState = state;
        //sceneState?.OnEnter();
    }

}

StartScene.cs

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class StartScene :SceneState
{

    /// <summary>
    /// 场景名称
    /// </summary>
    readonly string sceneName = "Start";
    PanelManager panelManager;
    public override void OnEnter()
    {
        panelManager = new PanelManager();
        if (SceneManager.GetActiveScene().name != sceneName)
        {
            SceneManager.LoadScene(sceneName);
            SceneManager.sceneLoaded += SceneLoaded;//添加方法的绑定
        }
        else
        {
            panelManager.Push(new StartPanel());
        }
    }
    public override void OnExit()
    {
        SceneManager.sceneLoaded -= SceneLoaded;
        panelManager.PopAll();
    }

    /// <summary>
    /// 场景加载完毕后执行的方法
    /// </summary>
    /// <param name="scene"></param>
    /// <param name="load"></param>
    private void SceneLoaded(Scene scene, LoadSceneMode load)
    {
        panelManager.Push(new StartPanel());
        //GameRoot.Instance.SetAction(panelManager.Push);优化
        Debug.Log($"{sceneName}场景加载完毕");
    }

}

MainScene.cs

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainScene :SceneState
{
    // Start is called before the first frame update
    /// <summary>
    /// 场景名称
    /// </summary>
    readonly string sceneName = "Main";
    PanelManager panelManager;
    public override void OnEnter()
    {
        panelManager = new PanelManager();
        if (SceneManager.GetActiveScene().name != sceneName)
        {
            SceneManager.LoadScene(sceneName);
            SceneManager.sceneLoaded += SceneLoaded;//添加方法的绑定
        }
        else
        {
            panelManager.Push(new StartPanel());
            //GameRoot.Instance.SetAction(panelManager.Push);
        }
    }
    public override void OnExit()
    {
        SceneManager.sceneLoaded -= SceneLoaded;
        panelManager.PopAll();
    }

    /// <summary>
    /// 场景加载完毕后执行的方法
    /// </summary>
    /// <param name="scene"></param>
    /// <param name="load"></param>
    private void SceneLoaded(Scene scene, LoadSceneMode load)
    {
        panelManager.Push(new MainPanel());
        //GameRoot.Instance.SetAction(panelManager.Push);
        Debug.Log($"{sceneName}场景加载完毕");
    }
}

MainPanel.cs

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// Main场景的主面板
/// </summary>
public class MainPanel : BasePanel
{
    static readonly string path = "Prefabs/UI/Panel/MainPanel";
    public MainPanel() : base(new UIType(path)) { }

    public override void OnEnter()
    {
        base.OnEnter();
        UITool.GetOrAddComponentInChildren<Button>("Quit").onClick.AddListener(() =>
        {
            //点击事件可以写在这里面
            GameRoot.Instance.SceneSystem.SetScene(new StartScene());
            //PanelManager.Pop();
            Pop();
        });

    }
}

GameRoot.cs

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
///管理全局的一些东西
/// </summary>
public class GameRoot : MonoBehaviour
{
    public static GameRoot Instance { get; private set; }
    public SceneSystem SceneSystem { get; private set; }

    /// <summary>
    /// 显示一个面板
    /// </summary>
    public UnityAction<BasePanel> Push { get; private set; }

    public void SetAction(UnityAction<BasePanel> push)
    {
        Push = push;
    }

    private void Awake()
    {
        if (Instance == null)
            Instance = this;
        else
            Destroy(gameObject);
        SceneSystem = new SceneSystem();

        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        SceneSystem.SetScene(new StartScene());
    }
}

   

免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空