0


Unity UI框架

窗口管理

初始化

在场景中挂载一个脚本,用于创建初始界面

using UnityEngine;

//创建开始界面
public class CreateStartPanel : MonoBehaviour
{
    //面板顺序管理器
    PanelStack panelStack;

    void Awake() 
    {
        panelStack = new PanelStack();
    }

    void Start()
    {
        //打开初始面板
        panelStack.Push(new StartPanel());
    }
}

初始界面

初始界面面板的控制脚本,不用挂载到面板的预制体上

生成面板的时候,直接new一个该类的实例即可

初始界面有一个Open按钮,在预制体中是没有添加点击事件

在面板打开之后,动态地添加点击事件,按下可以打开一个子窗口

using UnityEngine;
using UnityEngine.UI;

//开始面板
public class StartPanel : BasePanel
{
    //面板存储在Resource/Prefabs目录下:路径从Prefabs开始,需要包含文件名,不用加文件后缀
    static readonly string path="Prefabs/StartPanel";

    public StartPanel():base(new PanelType(path)){ Debug.Log("派生类构造函数"); }

    //重写进入函数
    public override void OnEnter()
    {
        //给指定的按钮添加点击事件
        panelExtensionTool.GetOrAddComponentInChildren<Button>("OpenAnotherPanelBtn").onClick.AddListener(()=>
        {
            //打开子窗口
            PanelStack.Push(new ChildrenPanel());
        }
        );

        panelExtensionTool.GetOrAddComponentInChildren<Button>("MainSceneBtn").onClick.AddListener(() =>
        {
            //使用单例切换场景
            GameManager.instance.sceneSystem.SetScene(new MainScene());
            //弹出场景中的所有面板
            PanelStack.PopAll();
        }
        );
    }

    //重写退出函数
    public override void OnExit()
    {
        //关闭面板
        PanelManager.DestroyPanel(this.PanelType);
    }

    //暂停:使窗口不能交互
    public override void OnPause()
    {
        //也可以写在基类中
        panelExtensionTool.GetOrAddComponent<CanvasGroup>().blocksRaycasts = false;
    }
    
    //恢复:使窗口可以交互
    public override void OnResume()
    {
        panelExtensionTool.GetOrAddComponent<CanvasGroup>().blocksRaycasts = true;
    }
}

子窗口

子界面有一个Close按钮,在预制体中是没有添加点击事件

在面板打开之后,动态地添加点击事件,按下可以关闭这个子窗口

using UnityEngine;
using UnityEngine.UI;

//子面板
public class ChildrenPanel: BasePanel
{
    static readonly string path = "Prefabs/ChildrenPanel";

    public ChildrenPanel() : base(new PanelType(path)) { }

    public override void OnEnter()
    {
        //给指定的按钮添加点击事件
        panelExtensionTool.GetOrAddComponentInChildren<Button>("Close").onClick.AddListener(() =>
        {
            //关闭面板
            PanelStack.Pop();
        }
        );
    }

    public override void OnExit()
    {
        PanelManager.DestroyPanel(this.PanelType);
    }

    public override void OnPause()
    {
        panelExtensionTool.GetOrAddComponent<CanvasGroup>().blocksRaycasts = false;
    }

    public override void OnResume()
    {
        panelExtensionTool.GetOrAddComponent<CanvasGroup>().blocksRaycasts = true;
    }
}

面板类型信息

面板的类型信息,用于区分不同类型的面板

/// <summary>
/// 面板的类型信息
/// </summary>
public class PanelType
{
    /// <summary>
    /// 面板的名称
    /// </summary>
    public string Name;

    /// <summary>
    /// 面板的存储路径
    /// </summary>
    public string Path;

    public PanelType(string path) 
    {
        Path = path;
        //截取斜杠后的全部字符
        Name = path.Substring(path.LastIndexOf('/') + 1);
    }
}

基础面板

每一个面板都需要基础这个基础面板类

基础面板类保存面板的类型信息

对于面板有四个基本状态,分别是打开、关闭、暂停、恢复

在状态发生转换时,需要执行对应的函数,完成相应的操作

这四个事件函数在基类中被声明为虚函数,需要继承的派生类将它们重写、实现

此外,基础面板还保存了面板扩展工具、面板栈、面板管理器的引用及其属性,以及相应的初始化函数,这样就可以方便地对面板进行管理

using UnityEngine;

/// <summary>
/// 基础面板(基类):包含面板的状态信息,以及在派生类中要重写的函数
/// </summary>
public class BasePanel
{
    /// <summary>
    /// 面板类型信息:外部只读,内部可写
    /// </summary>
    /// <value></value>
    public PanelType PanelType{get;private set;}

    /// <summary>
    /// 基础面板的构造函数
    /// </summary>
    /// <param name="panelType"></param>
    public BasePanel(PanelType panelType)
    {
        PanelType=panelType;
        Debug.Log("基类构造函数");
    }

    /// <summary>
    /// 打开面板时执行
    /// </summary>
    public virtual void OnEnter(){ }

    /// <summary>
    /// 停用面板时执行
    /// </summary>
    public virtual void OnPause(){ }

    /// <summary>
    /// 恢复面板时执行
    /// </summary>
    public virtual void OnResume(){ }

    /// <summary>
    /// 关闭面板时执行
    /// </summary>
    public virtual void OnExit(){ }

    //面板扩展工具
    public PanelExtensionTool panelExtensionTool {get;private set;}

    //初始化面板扩展工具
    public void Initialize(PanelExtensionTool tool)
    {
        panelExtensionTool = tool;
    }

    //面板之栈
    public PanelStack PanelStack { get;private set;}

    public void Initialize(PanelStack panelStack)
    {
        PanelStack = panelStack;
    }

    //面板管理器
    public PanelManager PanelManager { get;private set;}

    public void Initialize(PanelManager panelManager)
    {
        PanelManager = panelManager;
    }

}

面板扩展工具

用于获取面板及其子对象上的组件,也可以为其添加组件

using UnityEngine;

/// <summary>
/// 面板扩展工具:给当前活动面板或其子对象添加组件
/// </summary>
public class PanelExtensionTool
{
    /// <summary>
    /// 顶层的活动面板
    /// </summary>
    GameObject topPanel;

    //构造函数
    public PanelExtensionTool(GameObject panel)
    {
        topPanel = panel;
    }

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

    /// <summary>
    /// 根据名称,获取指定子对象上的组件,如果没有,则为其添加
    /// </summary>
    /// <param name="name">子对象的名称</param>
    /// <typeparam name="T">组件类型</typeparam>
    /// <returns>组件</returns>
    public T GetOrAddComponentInChildren<T>(string name) where T:UnityEngine.Component
    {
        GameObject child = FindChildGameObject(name);
        if(child)
        {
            if(child.GetComponent<T>()==null)
                child.AddComponent<T>();
            return child.GetComponent<T>();
        }
        return null;
    }

    /// <summary>
    /// 根据名称查找顶层面板上的子对象:比如按钮
    /// </summary>
    /// <param name="name">子对象名称</param>
    /// <returns></returns>
    public GameObject FindChildGameObject(string name)
    {
        //存储顶层面板的子对象的全部组件的Transform属性
        Transform[] trans = topPanel.GetComponentsInChildren<Transform>();

        foreach (Transform item in trans)
        {
            //在transform属性数组中,找到指定名称的子对象
            if (item.name == name)
            {
                return item.gameObject;
            }
        }

        Debug.Log("找不到子对象");
        return null;
    }
}

面板之栈

用于管理面板的响应顺序

打开一个面板,入栈,其父面板需要暂停响应

关闭一个面板,出栈,其父面板需要恢复响应

栈顶的面板可以交互,其余面板需要处于暂停响应的状态

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 面板之栈:用于管理面板的响应顺序
/// </summary>
public class PanelStack
{
    /// <summary>
    /// 存储面板的栈
    /// </summary>
    private Stack<BasePanel> stack;

    /// <summary>
    /// 面板管理器
    /// </summary>
    private PanelManager panelManager;

    //面板基类引用
    private BasePanel panel;

    /// <summary>
    /// 初始化面板之栈
    /// </summary>
    public PanelStack()
    {
        stack = new Stack<BasePanel>();
        panelManager = new PanelManager();
    }

    /// <summary>
    /// 打开面板时将面板入栈
    /// </summary>
    /// <param name="nextPanel"></param>
    public void Push(BasePanel nextPanel)
    {
        //如果还有面板在显示
        if(stack.Count>0)
        {
            //取栈顶
            panel = stack.Peek();
            //暂停上一个面板
            panel.OnPause();
        }
        //新面板入栈
        stack.Push(nextPanel);
        //获取一个面板
        GameObject panelToShow = panelManager.ShowPanel(nextPanel.PanelType);

        //初始化面板的工具
        nextPanel.Initialize(new PanelExtensionTool(panelToShow));
        //初始化面板的面板顺序管理器
        nextPanel.Initialize(this);
        //初始化面板管理器
        nextPanel.Initialize(panelManager);
        //面板进入时要执行的任务
        nextPanel.OnEnter();
    }

    /// <summary>
    /// 关闭面板时将面板出栈:弹出栈顶的面板,再恢复新栈顶的面板
    /// </summary>
    public void Pop()
    {
        if(stack.Count>0)
        {
            stack.Pop().OnExit();

            //栈顶的面板退出
            //stack.Peek().OnExit();
            //面板出栈
            //stack.Pop();
        }
        //恢复下层面板
        if(stack.Count>0)
            stack.Peek().OnResume();
    }

    /// <summary>
    /// 关闭所有面板并执行其退出函数
    /// </summary>
    public void PopAll()
    {
        while (stack.Count > 0)
            stack.Pop().OnExit();
    }
}

面板管理器

用于创建或销毁面板,并存储打开的面板的信息

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 面板管理器:创建或销毁面板,并存储打开的面板的信息
/// </summary>
public class PanelManager
{
    /// <summary>
    /// 用字典存储所有打开的面板,每一个面板类对应一个面板
    /// </summary>
    private Dictionary<PanelType,GameObject> panelDict;

    /// <summary>
    /// 构造函数:初始化字典
    /// </summary>
    public PanelManager()
    {
        panelDict = new Dictionary<PanelType, GameObject>();
    }

    /// <summary>
    /// 显示一个面板
    /// </summary>
    /// <param name="type">UI类型</param>
    /// <returns></returns>
    public GameObject ShowPanel(PanelType type)
    {
        //将画布指定为面板的父对象
        GameObject parent=GameObject.Find("Canvas");
        if(!parent)
        {
            UnityEngine.Debug.Log("Error:画布不存在");
            return null;
        }
        //如果字典中有指定的面板的信息,则返回这个面板的对象
        if(panelDict.ContainsKey(type))
            return panelDict[type];
        //将指定的面板克隆到画布上
        GameObject panel = GameObject.Instantiate(Resources.Load<GameObject>(type.Path),parent.transform);
        //设定面板对象的名字
        panel.name=type.Name;
        //加入字典
        panelDict.Add(type,panel);
        return panel;
    }

    /// <summary>
    /// 关闭一个面板
    /// </summary>
    public void DestroyPanel(PanelType type) 
    {
        if(panelDict.ContainsKey(type))
        {
            //销毁指定的面板
            Object.Destroy(panelDict[type]);
            //从字典移除该面板的键值对
            panelDict.Remove(type);
        }
    }
}

场景管理

游戏管理器

用于加载初始场景

保存场景管理系统的引用,通过提供单例,让其他脚本可以获取到场景管理系统

using UnityEngine;

public class GameManager : MonoBehaviour
{
    //单例模式:外部可以通过这个实例,调用场景管理系统内的函数
    public static GameManager instance{get;private set;}

    //场景管理系统
    public SceneSystem sceneSystem{get;private set;}

    private void Awake() 
    {
        //如果单例还没有实例化,则赋值。如果已经实例化,则销毁自身。
        if (instance == null)
            instance = this;
        else
            Destroy(gameObject);

        sceneSystem = new SceneSystem();

        //将单例放入DontDestroyOnLoad,确保切换场景之后不会丢失,可以继续使用
        DontDestroyOnLoad(gameObject);
    }

    private void Start() 
    {
        //打开初始场景
        sceneSystem.SetScene(new StartScene());
    }
}

场景基类

将场景基类写为抽象类,用于给具体的场景继承,必须实现它的两个虚函数

public abstract class SceneBase
{
    //场景进入时要执行的操作
    public abstract void OnEnter();

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

场景管理系统

用于加载和退出场景

//场景的状态管理系统
public class SceneSystem
{
    //场景基类
    SceneBase sceneBase;

    //设置场景:退出前一个场景,加载后一个场景
    public void SetScene(SceneBase scene)
    {
        sceneBase?.OnExit();
        sceneBase = scene;
        sceneBase?.OnEnter();
    }
}

初始场景

场景继承场景基类,并实现其中状态转换时需要执行的两个函数:进入、退出

如果进入时,当前场景已加载,则显示要展示的面板

如果进入时没有加载场景,则加载场景

使用事件函数(委托)对场景的加载进行监听

事件函数SceneLoad的第一个参数Scene是场景,第二个参数是加载模式

无需手动为其赋值,只需要将事件函数绑定到SceneManager.sceneLoaded即可

SceneManager.sceneLoaded += SceneLoaded;

如果监听到场景加载,Scene变化吗,则执行SceneLoaded函数,用于执行加载面板等操作

using UnityEngine.SceneManagement;

public class StartScene:SceneBase
{
    //场景名
    readonly string sceneName="StartScene";

    //场景顺序管理器
    PanelStack panelStack;

    public override void OnEnter()
    {
        panelStack = new PanelStack();
        //当前的活动场景不是指定的场景,就加载指定的场景
        if(SceneManager.GetActiveScene().name!=sceneName)
        {
            SceneManager.LoadScene(sceneName);
            SceneManager.sceneLoaded+=SceneLoaded;
        }
        //如果是指定的场景,那么就显示要显示的面板
        else panelStack.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)
    {
        // 显示初始面板
        panelStack.Push(new StartPanel());
    }
}

主场景

与初始场景类似

using UnityEngine.SceneManagement;

public class MainScene : SceneBase
{
    //场景名
    readonly string sceneName="MainScene";

    //面板栈
    PanelStack panelStack;

    public override void OnEnter()
    {
        panelStack = new PanelStack();

        if(SceneManager.GetActiveScene().name!=sceneName)
        {
            SceneManager.LoadScene(sceneName);
            SceneManager.sceneLoaded += SceneLoaded;
        }
        else panelStack.Push(new MainPanel());
    }

    public override void OnExit()
    {
        SceneManager.sceneLoaded -= SceneLoaded;
    }

    void SceneLoaded(Scene scene,LoadSceneMode load)
    {
        panelStack.Push(new MainPanel());
    }
}

主场景中的面板

仅仅用于测试能否从主场景返回初始场景

using UnityEngine;
using UnityEngine.UI;

//主面板
public class MainPanel : BasePanel
{
    static readonly string path = "Prefabs/MainPanel";

    public MainPanel() : base(new PanelType(path)){}

    public override void OnEnter()
    {
        panelExtensionTool.GetOrAddComponentInChildren<Button>("Exit").onClick.AddListener(() =>
        {
            //返回初始场景
            GameManager.instance.sceneSystem.SetScene(new StartScene());
            PanelStack.PopAll();
        }
        );
    }

    public override void OnExit()
    {
        PanelManager.DestroyPanel(this.PanelType);
    }

    public override void OnPause()
    {
        panelExtensionTool.GetOrAddComponent<CanvasGroup>().blocksRaycasts = false;
    }

    public override void OnResume()
    {
        panelExtensionTool.GetOrAddComponent<CanvasGroup>().blocksRaycasts = true;
    }
}

效果

运行前

开始场景

打开子窗口

此时无法点击主面板的按钮

关闭子窗口

主面板上的按钮又可以点击了

跳转到另一个场景

回到初始场景

标签: unity ui

本文转载自: https://blog.csdn.net/weixin_43673589/article/details/121896806
版权归原作者 海 月 所有, 如有侵权,请联系我们删除。

“Unity UI框架”的评论:

还没有评论