在之前做项目的学习到要让UI生成在我们制作成预制体之前的位置(一般情况下都是这样)
需要把对象池里面的生成函数变成这样
public GameObject GetObject(string name,Transform parentTransfrom,bool InWorldSpace)
{
GameObject obj = null;
if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)
{
Debug.Log("Find item" + name);
obj = poolDic[name][0];
//obj.transform.position = location;
poolDic[name].RemoveAt(0);
}
else
{
obj = GameObject.Instantiate(Resources.Load(name), parentTransfrom, InWorldSpace);
//Debug.Log(name);
}
obj.SetActive(true);
return obj;
}
生成函数改成这样
GameObject curPanel = PoolManager.GetInstance().GetObject(GetPanelString(panelType), CanvasTransform.position, false);
其他不需要做任何修改
目的
这只是针对大家做一些小游戏的时候可能用到的UI框架,主要目的在于:
- 不需要在Scene面板提前创建好UI,界面看起来更整洁
- 对象池保证UI重复利用,减少反复生成销毁的内存开销
- 栈管理UI,将UI分为两类:栈顶UI、其他UI;可分别对其处理。比如实现栈顶UI可交互,其他UI不交互,这里UI指代的是面板。
UI的获取和放回使用对象池实现,
实现 对象池用于得到不同面板的预制体,遇事不决对象池
using System.Collections; using System.Collections.Generic; using UnityEngine; ///单例基类/// 对象池管理器,可以调用其中的函数获取对象 /// list存储游戏物体,Dictionary制作抽屉 /// public class PoolManager : BaseManager{ public Dictionary > poolDic = new Dictionary >(); /// /// 从池子里面取得物品 /// /// ///public GameObject GetObject(string name, Vector3 location) { GameObject obj = null; if(poolDic.ContainsKey(name) && poolDic[name].Count > 0) { //Debug.Log("Find item" + name); obj = poolDic[name][0]; obj.transform.position = location; poolDic[name].RemoveAt(0); } else { obj = GameObject.Instantiate(Resources.Load (name), location, Quaternion.identity); //Debug.Log(name); } obj.SetActive(true); return obj; } public void PushObj(string name, GameObject obj) { obj.SetActive(false); //已经拥有抽屉 if(poolDic.ContainsKey(name)) { poolDic[name].Add(obj); } //里面没有抽屉 else { poolDic.Add(name, new List () { obj }); } } /// /// 场景切换时调用 /// public void Clear() { poolDic.Clear(); } }
继承的类就能开单例
public class BaseManagerBasePanelwhere T : new() { private static T instance; public static T GetInstance() { if (instance == null) { instance = new T(); return instance; } else { return instance; } } }
BasePanel,作为不同类型Panel的抽象基类,提供三个方法接口,继承Mono,其子类可以直接挂载在物体上。作用如注释。
using System.Collections;
using System.Collections.Generic;
public abstract class BasePanel:MonoBehaviour
{
///
/// 开启面板的时候调用
///
public abstract void OnEnter();
///
/// 当前面板在栈中而不是在栈顶的时候调用
///
public abstract void OnPause();
///
/// 当前面板再次处于栈顶的时候调用
///
public abstract void OnResume();
///
/// 关闭当前面板的时候调用
///
public abstract void OnExit();
}
UIManager
管理所有的UI,提供生成UI和关闭UI的接口,制定调用BasePanel的三个函数的逻辑。
using System.Collections; using System.Collections.Generic; using UnityEngine; ///举个/// 管理所有UI,由于项目并不是很大,所以现阶段没有使用json存储所有的UI类型,UI的存取通过对象池来获得 /// 在场景切换的时候需要把数据结构清空 /// public class UIManager : BaseManager{ /// /// 管理当前场景的UI的栈 /// private StackpanelStack; /// /// 管理状态(面板)的字典 /// private DictionarypanelDict; private Transform canvasTransform; /// /// 返回面板类型对应字符串,便于对象池生成 /// /// private string GetPanelString(PanelType type) { switch (type) { case PanelType.StartPanel: return "StartPanel"; case PanelType.TestPanel: return "TestPanel"; case PanelType.TestPanel2: return "TestPanel2"; default: Debug.Log($"不存在{type.ToString()}面板"); break; } return " "; } private Transform CanvasTransform { get { if(canvasTransform == null) { canvasTransform = GameObject.Find("Canvas").transform; } return canvasTransform; } } public GameObject currentPanel; public UIManager() { panelStack = new Stack(); } private BasePanel GetUI(PanelType panelType) { if(panelDict == null) { panelDict = new Dictionary (); } BasePanel panel; if(!panelDict.TryGetValue(panelType, out panel)) { GameObject curPanel = PoolManager.GetInstance().GetObject(GetPanelString(panelType), CanvasTransform.position); curPanel.transform.SetParent(CanvasTransform); panel = curPanel.GetComponent (); panelDict.Add(panelType, panel); } else { panel = panelDict[panelType]; GameObject curPanel = PoolManager.GetInstance().GetObject(GetPanelString(panelType), CanvasTransform.position); } return panel; } /// /// 推入一个面板 /// /// public void PushPanel(PanelType panelType) { if(panelStack == null) { panelStack = new Stack(); } if(panelStack.Count > 0) { BasePanel topPanel = panelStack.Peek(); topPanel.OnPause(); } BasePanel panel = GetUI(panelType); panelStack.Push(panel); panel.OnEnter(); } /// /// 弹出面板 /// public void PopPanel() { if(panelStack == null) { panelStack = new Stack(); } if(panelStack.Count<=0) { return; } //退出栈顶面板 BasePanel topPanel = panelStack.Pop(); topPanel.OnExit(); //恢复上一个面板 if (panelStack.Count > 0) { BasePanel panel = panelStack.Peek(); panel.OnResume(); } } }
实现一个负责开启两个面板的主面板
testButton1可以用find也可以直接拖拽赋值
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class StartPanel : BasePanel
{
public Button testButton1;
public Button testButton2;
private CanvasGroup m_CanvasGroup;
private void Start()
{
testButton1.onClick.AddListener(test1ButtonEvent);
testButton2.onClick.AddListener(test2ButtonEvent);
m_CanvasGroup = gameObject.GetComponent();
}
public override void OnEnter()
{
Debug.Log("进入开始面板");
}
private void test1ButtonEvent()
{
UIManager.GetInstance().PushPanel(PanelType.TestPanel);
}
private void test2ButtonEvent()
{
UIManager.GetInstance().PushPanel(PanelType.TestPanel2);
}
public override void OnExit()
{}
public override void OnPause()
{
Debug.Log("开始界面被覆盖");
m_CanvasGroup.blocksRaycasts = false;
}
public override void OnResume()
{
m_CanvasGroup.blocksRaycasts = true;
}
}
GameManager
这里只负责一开始生成开始面板,挂载到主相机上面
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
UIManager.GetInstance().PushPanel(PanelType.StartPanel);
}
// Update is called once per frame
void Update()
{
}
}
运行结果如下
测试面板,只能开启其中一个,一个开启后,StartPanel的按钮将不会再生效,直到关闭
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TestPanel : BasePanel
{
public Button exitButton;
private CanvasGroup m_CanvasGroup;
private void Start()
{
exitButton.onClick.AddListener(exitButtonAction);
}
public override void OnEnter()
{
Debug.Log("打开测试界面1");
}
public override void OnExit()
{
Debug.Log("测试界面1退出");
PoolManager.GetInstance().PushObj("TestPanel", this.gameObject);
}
public override void OnPause()
{
Debug.Log("测试界面1被覆盖");
m_CanvasGroup.blocksRaycasts = false;
}
public override void OnResume()
{
Debug.Log("测试界面1恢复");
m_CanvasGroup.blocksRaycasts = true;
}
private void exitButtonAction()
{
UIManager.GetInstance().PopPanel();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TestPanel2 : BasePanel
{
public Button exitButton;
private CanvasGroup m_CanvasGroup;
private void Start()
{
exitButton.onClick.AddListener(exitButtonAction);
}
public override void OnEnter()
{
Debug.Log("打开测试界面2");
}
public override void OnExit()
{
Debug.Log("测试界面2退出");
PoolManager.GetInstance().PushObj("TestPanel2", this.gameObject);
}
public override void OnPause()
{
Debug.Log("测试界面2被覆盖");
m_CanvasGroup.blocksRaycasts = false;
}
public override void OnResume()
{
Debug.Log("测试界面2恢复");
m_CanvasGroup.blocksRaycasts = true;
}
private void exitButtonAction()
{
UIManager.GetInstance().PopPanel();
}
}
分别挂载到两个面板上
接下来测试
1. 按下测试按钮一、testPanel1出现
2. 尝试按下测试按钮二、没有反应
3. 关闭测试面板一后再次按下测试按钮二,出现测试面板二,并且一失活进入对象池
4. 同理尝试按下测试按钮一、没有反应
ヾ(✿゚▽゚)ノ说明成功了
ps:这里生成的UI并没有按照预制体之前的位置放在左右上角,根据更新所示的那样就行了
很多情况下,当一个UI出现时我们都需要把其他UI的交互关闭,否则经常会造成各种bug,所以管理UI还是非常重要的。



