窗口管理
初始化
在场景中挂载一个脚本,用于创建初始界面
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;
}
}
效果
运行前
开始场景
打开子窗口
此时无法点击主面板的按钮
关闭子窗口
主面板上的按钮又可以点击了
跳转到另一个场景
回到初始场景
|