当前位置:   article > 正文

unity 简单易懂的ui框架_scenesystem

scenesystem

效果预览

在这里插入图片描述

基础面板所需要的代码

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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这些文件的路径随后需要通过代码进行获取:
在这里插入图片描述

BasePanel 基础面板类

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

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

  1. 处于操作当前面板的状态(此时需要调用onEnter函数,因此下面这样书写)
  2. 暂停面板状态,比如说此时弹出了一个新的子面板,此时不能对原来的面板进行操作(OnPause)
  3. 恢复面板状态,比如说从其他面板回到这个面板时,此时可以重新操作,于是需要结束暂停面板时的操作
  4. 退出面板状态,退出当前面板时,需要执行一些操作
    因此需要这四个函数
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() { }


}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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

UIManager UI管理器

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

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

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

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

在这里插入图片描述
Instantiate函数实例化是将original对象的所有子物体和子组件完全复制,成为一个新的对象。这个新的对象拥有与源对象完全一样的东西,包括坐标值等。
original:用来做复制操作的对像物体,源对象
position:实例化的对象的位置坐标
rotation:实例化的对象的旋转坐标(旋转四元数)
parent:实例化对象的父对象,就是给这个实例化对象找的爹,在完成实例化函数处理后,实例化对象将在父对象下,是父对象的子对象
instantiateWorldSpace(老的叫WorldSpaceStays):这个值为TRUE,表示实例化对象相对于世界坐标系的位置(是位置,不是坐标值,比如实例化前在Canvas左上角,实例化后还在左上角)不变,相对于父对象的坐标值变了。为false,表示实例化对象相对于父对象的坐标值不变,但在世界坐标系中的位置变了。
在这里插入图片描述
这两个函数的区别是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);
        }
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

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作为参数,然后去调用基类的构造函数。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

场景跳转

接下来实现场景间的跳转

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();

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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

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}场景加载完毕");
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

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

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}场景加载完毕");
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

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();
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

再创建一个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());
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

然后在StartPanel中添加Start的按键:
在这里插入图片描述
并且在StartPanel的面板中添加跳转的按钮,然后想要按下按钮时跳转,写下以下脚本:
在这里插入图片描述
此时,点击按钮便可以成功从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()
    {

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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

此时按下主面板的开始建在这里插入图片描述
即可跳转到Start界面并推出Panel

在这里插入图片描述

场景间任意跳转

当从Start场景跳转到主场景时会发现,GameRoot就没了,这样就不好管理跳转回去,
为此,我们添加这样的函数:
在这里插入图片描述
此时即使跳转,GameRoot也不会消失

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

在这里插入图片描述

于是需要在MainPanel中添加一个右上角的关闭按键
在这里插入图片描述

在这里插入图片描述
做到这里后即可实现按下主场景中的叉键场景跳转,但是出现了一个问题,当再次回到主场景时,多了一个GameRoot在这里插入图片描述
为了防止这种情况,我们可以
在这里插入图片描述

代码优化

在这里插入图片描述

然后在每个场景结束时,都弹出所有面板
在这里插入图片描述
同理MainScene也这样做:
在这里插入图片描述
在这里插入图片描述
像这样:
在这里插入图片描述

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

然后就可以在Scene的脚本中:
在这里插入图片描述
在这里插入图片描述

完整代码

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);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

BasePanel.cs

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

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

  1. 处于操作当前面板的状态(此时需要调用onEnter函数)
  2. 暂停面板状态,比如说此时弹出了一个新的子面板,此时不能对原来的面板进行操作(OnPause)
  3. 恢复面板状态,比如说从其他面板回到这个面板时,此时可以重新操作,于是需要结束暂停面板时的操作
  4. 退出面板状态,退出当前面板时,需要执行一些操作
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();
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

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);
        }
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

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();
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

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());
        });
    }


}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

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;
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

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();
        });

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

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场景的主面板
/

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246

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();

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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();
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

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}场景加载完毕");
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

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}场景加载完毕");
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

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();
        });

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

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());
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/90698
推荐阅读
相关标签
  

闽ICP备14008679号