Unity3D UI代码框架学习

49 阅读2分钟

学习B站简问游戏开发的代码框架入门,自己修改了一点。

具体控件了解,需要时查书

主要使用了单例模式

image.png

image.png

Panel部分

一个父类BasePanel,一个UIType。一个panel存储一个界面,比如开始界面,登录界面,设置界面的UI预制件。UIType记录预制件的名称和文件路径

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


public class BasePanel
{
    public UIType uiType;
    // UI对应的object
    public GameObject ActiveObj;
    public BasePanel(UIType uiType)
    {
        this.uiType = uiType;
    }
    public virtual void OnStart()
    {
        Debug.Log($"{uiType.Name}开始使用");
        UIMethod.Instance.GetOrAddComponent<CanvasGroup>(ActiveObj).interactable = true;
    }

    public virtual void OnEnable()
    {
        UIMethod.Instance.GetOrAddComponent<CanvasGroup>(ActiveObj).interactable = true;
    }
    public virtual void OnDisable()
    {
        UIMethod.Instance.GetOrAddComponent<CanvasGroup>(ActiveObj).interactable = false;
    }
    public virtual void OnDestroy()
    {
        UIMethod.Instance.GetOrAddComponent<CanvasGroup>(ActiveObj).interactable = false;
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class StartPanel : BasePanel
{
    private static string name = "StartPanel";
    private static string path = "Panel/StartPanel";
    public static readonly UIType startUIType = new UIType(name, path);

    public StartPanel(): base(startUIType)
    {

    }
    public override void OnDestroy()
    {
        base.OnDestroy();
    }

    public override void OnDisable()
    {
        base.OnDisable();
    }

    public override void OnEnable()
    {
        base.OnEnable();
    }

    public override void OnStart()
    {
        base.OnStart();
        Scene2 scene2 = new Scene2();
        UIMethod.Instance.GetOrAddSingleComponentInChild<Button>(ActiveObj, "StartButton").onClick.AddListener(() => SceneController.Instance.LoadScene(scene2.sceneName, scene2));
        UIMethod.Instance.GetOrAddSingleComponentInChild<Button>(ActiveObj, "StartButton").GetComponentInChildren<TextMeshProUGUI>().text = "Load Scene2";
    }
}

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

public class UIType
{
    // 自己定义的一种数据结构,不需要挂载mono
    private string name;
    private string path;

    public string Name { get => name; }
    public string Path { get => path; }

    // 构造函数
    /// <summary>
    /// 获得UI信息
    /// </summary>
    /// <param name="uiName">此UI对于Panel的名称</param>
    /// <param name="uiPath">此UI对于Panel的路径</param>

    public UIType(string uiName, string uiPath)
    {  
        name = uiName;
        path = uiPath;
    }

}

Scene部分

创建,控制场景的加载等。

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

/// <summary>
/// 场景的父类
/// </summary>
public abstract class SceneBase
{
    /// <summary>
    /// 进入场景时执行的方法
    /// </summary>
    public abstract void EnterScene();
    /// <summary>
    /// 退出场景时执行的方法
    /// </summary>
    public abstract void ExitScene();

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

/// <summary>
/// 控制场景切换
/// </summary>
public class SceneController
{
    /// <summary>
    /// scene名称——scene场景
    /// </summary>
    public Dictionary<string, SceneBase> dictionaryScene;
    private static SceneController instance;
    public static SceneController Instance
    {
        get
        {
            if (instance == null)
            {
                Debug.LogWarning("SceneController实例不存在,创建");
                instance = new SceneController();
                
            }
            return instance;
        }
    }

    private SceneController()
    {
        dictionaryScene = new Dictionary<string, SceneBase>();
    }
    /// <summary>
    /// 加载场景
    /// </summary>
    /// <param name="sceneName">场景名称</param>
    /// <param name="scene">场景</param>
    public void LoadScene(string sceneName, SceneBase scene)
    {
        // 记录场景信息
        if (!dictionaryScene.ContainsKey(sceneName))
        {
            dictionaryScene.Add(sceneName, scene);
        }
        // 退出当前激活状态的场景
        if (dictionaryScene.ContainsKey(SceneManager.GetActiveScene().name))
        {
            dictionaryScene[SceneManager.GetActiveScene().name].ExitScene();
        }
        else
        {
            Debug.LogWarning($"SceneDictionary中不存在{SceneManager.GetActiveScene().name}");
        }

        // 弹出旧场景所有panel
        UIManager.Instance.PopAll();

        // 加载新指定场景
        SceneManager.LoadScene(sceneName);
        scene.EnterScene();
    }
}

public class Scene1 : SceneBase
{
    public readonly string sceneName = "Scene1";
    public override void EnterScene()
    {
        Debug.Log($"Enter Scene: {sceneName}");
    }

    public override void ExitScene()
    {
        Debug.Log($"Exit Scene: {sceneName}");
    }
}

UIMethod和UIManager

对UI物体的一些操作,比如界面之间的跳转,利用栈结构实现。组件的查找添加等。

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

public class UIManager
{
    // UIManager静态实例指针
    private static UIManager instance;
    public static UIManager Instance
    {
        get
        {
            if (instance == null)
            {
                // 如果实例为空
                Debug.LogWarning("UIManager实例不存在,创建");
                instance = new UIManager();
            }
            return instance;
        }
    }
    /// <summary>
    /// 存储panel ui的栈
    /// </summary>
    public Stack<BasePanel> uiStack;

    /// <summary>
    /// 栈中的panel名称——panel物体信息
    /// </summary>
    public Dictionary<string, GameObject> uiDictionary;

    /// <summary>
    /// 当前场景canvas
    /// </summary>
    public GameObject canvasObj;
    private UIManager()
    {
        uiStack = new Stack<BasePanel>();
        uiDictionary = new Dictionary<string, GameObject>();
    }

    
    public GameObject GetSingleObject(UIType uiType)
    {
        // 如果dictionary中存在,直接返回
        if (uiDictionary.ContainsKey(uiType.Name))
            return uiDictionary[uiType.Name];
        // 如果canvas为空,那么获取
        if(canvasObj == null)
        {
            canvasObj = UIMethod.Instance.FindCanvas();
        }
        // 如果canvas不为空,那么在canvas中创建该uiType对应物体
        GameObject uiPrefab = Resources.Load<GameObject>(uiType.Path);
        
        if (uiPrefab == null)
        {
            // 创建失败,物体不存在
            Debug.LogError("UI prefab not found at path: " + uiType.Path);
            return null;
        }

        return GameObject.Instantiate<GameObject>(uiPrefab, canvasObj.transform);
    }

    public void Push(BasePanel basePanel)
    {
        Debug.Log($"Push {basePanel.uiType.Name} into uiStack");
        // 如果uiStack不为空,那么禁用、不显示现在栈顶UI
        if(uiStack.Count > 0)
        {
            Debug.Log($"禁用{uiStack.Peek().ActiveObj.name}");
            uiStack.Peek().OnDisable();
        }
        // 获取basePanel记录的uiObj
        GameObject uiObject = GetSingleObject(basePanel.uiType);
        // 记录新的UI元素
        uiDictionary.Add(basePanel.uiType.Name, uiObject);
        basePanel.ActiveObj = uiObject;
        
        if(uiStack.Count == 0 || uiStack.Peek().uiType.Name != basePanel.uiType.Name)
        {
            uiStack.Push(basePanel);
        }
        Debug.Log($"启用{uiObject.name}");
        basePanel.OnStart();
    }

    public void Pop()
    {
        // 首先栈不能为空
        if(uiStack.Count > 0)
        {
            // ————这部分是出栈————
            // 首先调用栈顶UI的disable方法和destroy方法
            BasePanel peekUI = uiStack.Peek();
            peekUI.OnDisable();
            peekUI.OnDestroy();
            // 销毁UI Object
            Debug.Log($"销毁面板{peekUI.uiType.Name}");
            GameObject.Destroy(uiDictionary[peekUI.uiType.Name]);
            uiDictionary.Remove(peekUI.uiType.Name);
            // 从uiStack中出栈
            uiStack.Pop();
            // ————出栈结束————

            // 如果UI栈不为空,那么启用顶层UI
            if (uiStack.Count > 0)
            {
                uiStack.Peek().OnEnable();
            }
        }
    }
    public void PopAll()
    {
        // 栈不能为空
        while (uiStack.Count > 0)
        {
            // ————这部分是出栈————
            // 首先调用栈顶UI的disable方法和destroy方法
            BasePanel peekUI = uiStack.Peek();
            peekUI.OnDisable();
            peekUI.OnDestroy();
            // 销毁UI Object
            GameObject.Destroy(uiDictionary[peekUI.uiType.Name]);
            uiDictionary.Remove(peekUI.uiType.Name);
            // 从uiStack中出栈
            uiStack.Pop();
        }
    }
}

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

public class UIMethod
{
    private static UIMethod instance;

    public static UIMethod Instance
    {
        get
        {
            if (instance == null)
            {
                Debug.LogWarning("UIMethod 不存在,创建");
                instance = new UIMethod();
            }
            return instance;
        }
    }

    /// <summary>
    /// 获得当前场景中的canvas,仅针对单canvas场景
    /// </summary>
    /// <returns>canvas object</returns>
    public GameObject FindCanvas()
    {
        Canvas canvas = GameObject.FindObjectOfType<Canvas>();
        if (canvas == null)
        {
            Debug.LogError("未在当前sense中找到canvas");
            return null;
        }
        return canvas.gameObject;
    }

    /// <summary>
    /// 查找panel中某个name的物体
    /// </summary>
    /// <param name="panel">需要查找的UI panel</param>
    /// <param name="childName">子物体名称</param>
    /// <returns></returns>
    public GameObject FindObjectInChildWithName(GameObject panel, string childName)
    {
        Transform[] childTransforms = panel.transform.GetComponentsInChildren<Transform>();

        foreach (Transform child in childTransforms)
        {
            if (child.gameObject.name == childName)
            {
                return child.gameObject;
            }
        }

        Debug.LogError($"未在 {panel.name} 中查找到子物体 {childName}");
        return null;
    }

    /// <summary>
    /// 获取ui object中的组件,如果没有就添加一个
    /// </summary>
    /// <typeparam name="T">组件类型</typeparam>
    /// <param name="uiObject">ui 物体</param>
    /// <returns></returns>
    public T GetOrAddComponent<T>(GameObject uiObject) where T : Component
    {
        T component = uiObject.GetComponent<T>();
        if (component == null)
        {
            Debug.LogWarning($"{uiObject.name}上不存在目标组件,已自动添加");
            component = uiObject.AddComponent<T>();
        }
        return component;
    }

    /// <summary>
    /// 获取指定名称的子物体的某组件
    /// </summary>
    /// <typeparam name="T">组件类型</typeparam>
    /// <param name="uiObject">母物体</param>
    /// <param name="childName">子物体名称</param>
    /// <returns></returns>
    public T GetOrAddSingleComponentInChild<T>(GameObject uiObject, string childName) where T : Component
    {
        Transform[] childTransforms = uiObject.GetComponentsInChildren<Transform>();

        foreach (Transform child in childTransforms)
        {
            if(child.gameObject.name == childName)
            {
                // 获取指定子物体的指定组件
                T component = child.gameObject.GetComponent<T>();
                if (component == null)
                {
                    Debug.LogWarning($"{child.gameObject.name}上不存在目标组件,已自动添加");
                    child.gameObject.AddComponent<T>();
                }
                // 返回该组件
                return component;
            }
        }
        // 未找到指定子物体
        Debug.LogWarning($"{uiObject.name}中未找到{childName}子物体");
        return null;
    }
}

个人感觉这个还是不够完善,以后会学更好的UI框架,仅作参考,暂时能用,而且这个只针对场景中一个canvas的情况使用