今天学习一下主界面的通用UI功能,比如说邮件页面、任务页面、背包页面、设置页面等等
并且创建一个UI池用于接下来可能的一些会频繁调用的UI
UI对象池
1.单例的实现
首先就是先实现一个UI池单例,并且保持不被GC
//首先创建静态实例,并且确保外部只能get而不能set
public static UIPool_Manager Instance { get; private set; }
//Awake的时候判断,如果单例不存在则将当前实例赋值给它
//如果单例已经存在则销毁当前实例
private void Awake()
{
if(Instance == null)
Instance = this;
else
Destroy(gameObject);
}
2.池结构的实现
首先定义一个类Pool,这是池中每一个对象的通用结构
分别定义tag,具体的引用对象以及所需要的大小
然后创建一个pools列表用来存放所有pool,这样就能在组件面板中定义需要的池以及大小
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
[SerializeField] private List<Pool> pools;
3.初始化对象池
接下来就是要初始化对象池
首先创立一个字典用于存储每一个tag对应的池队列
private Dictionary<string, Queue<GameObject>> poolDictionary = new Dictionary<string, Queue<GameObject>>();
遍历pools,然后根据设置的size创建实例,最后将每个poolItem加入字典中方便管理
private void InitializePools()
{
foreach(Pool pool in pools)
{
Queue<GameObject> objectPool = new Queue<GameObject>();
GameObject poolParent = new GameObject(pool.tag + "Pool");
poolParent.transform.SetParent(transform);
for(int i = 0; i< pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab, poolParent.transform);
obj.SetActive(false);
objectPool.Enqueue(obj);
}
poolDictionary.Add(pool.tag, objectPool);
}
}
4.从池中获取对象
定义一个函数提供给外部使用,用于从池中获取对象
如果池内还有空闲对象则就出池
如果池中没有空闲对象的话则到标签匹配的对应的池中进行扩容
public GameObject GetFromPool(string tag, Vector3 position, Quaternion rotation)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.Log($"Pool with tag {tag} doesn't exist.");
return null;
}
GameObject ObjectToSpawn;
if (poolDictionary[tag].Count > 0)
{
ObjectToSpawn = poolDictionary[tag].Dequeue();
ObjectToSpawn.transform.position = position;
ObjectToSpawn.transform.rotation = rotation;
ObjectToSpawn.SetActive(true);
}
else
{
Pool pool = pools.Find(p => p.tag == tag);
if (pool != null)
{
ObjectToSpawn = Instantiate(pool.prefab, position, rotation);
ObjectToSpawn.transform.SetParent(GameObject.Find(pool.tag + "Pool").transform);
}
else
{
Debug.LogWarning($"Pool configuration for tag {tag} not found.");
return null;
}
}
return ObjectToSpawn;
}
5.将对象返回到池中
提供一个方法给外界,在不需要使用到对象时将其返回对应的对象池、
先检查对象是否是池中的类型,如果确定是才能归还
public void ReturnToPool(string tag, GameObject obj)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.LogWarning($"Pool with tag {tag} doesn't exist. Destroying object instead.");
Destroy(obj);
return;
}
obj.SetActive(false);
poolDictionary[tag].Enqueue(obj);
}
整体代码
public class UIPool_Manager : MonoBehaviour
{
public static UIPool_Manager Instance { get; private set; }
private Dictionary<string, Queue<GameObject>> poolDictionary = new Dictionary<string, Queue<GameObject>>();
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
[SerializeField]
private List<Pool> pools;
private void Awake()
{
if(Instance == null)
Instance = this;
else
Destroy(gameObject);
InitializePools();
}
//初始化对象池
private void InitializePools()
{
foreach(Pool pool in pools)
{
Queue<GameObject> objectPool = new Queue<GameObject>();
GameObject poolParent = new GameObject(pool.tag + "Pool");
poolParent.transform.SetParent(transform);
for(int i = 0; i< pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab, poolParent.transform);
obj.SetActive(false);
objectPool.Enqueue(obj);
}
poolDictionary.Add(pool.tag, objectPool);
}
}
//从池中获取对象
public GameObject GetFromPool(string tag, Vector3 position, Quaternion rotation)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.Log($"Pool with tag {tag} doesn't exist.");
return null;
}
GameObject ObjectToSpawn;
if (poolDictionary[tag].Count > 0)
{
ObjectToSpawn = poolDictionary[tag].Dequeue();
ObjectToSpawn.transform.position = position;
ObjectToSpawn.transform.rotation = rotation;
ObjectToSpawn.SetActive(true);
}
else
{
Pool pool = pools.Find(p => p.tag == tag);
if (pool != null)
{
ObjectToSpawn = Instantiate(pool.prefab, position, rotation);
ObjectToSpawn.transform.SetParent(GameObject.Find(pool.tag + "Pool").transform);
}
else
{
Debug.LogWarning($"Pool configuration for tag {tag} not found.");
return null;
}
}
return ObjectToSpawn;
}
//将对象返回至对象池中
public void ReturnToPool(string tag, GameObject obj)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.LogWarning($"Pool with tag {tag} doesn't exist. Destroying object instead.");
Destroy(obj);
return;
}
obj.SetActive(false);
poolDictionary[tag].Enqueue(obj);
}
}
6.调用
在其它的地方从池中获取对象以及返回对象
private void OnBtn_SetClicked()
{
GameObject settingUI UIPool_Manager.Instance.GetFromPool("Settings",transform.position,Quaternion.identity);
if(settingUI !=null)
{
settingUI.SetActive(true);
}
}
private void On_Exist_AreaBtn_Clicked()
{
UIPool_Manager.Instance.ReturnToPool("Settings", currentUI);
}
滚动视口内容加载
1.预制体创建
做一个垂直滚动的邮件UI,然后Content中动态的加载Item
首先创建两个预制体,一个就是邮件页面的UI,具体一个Scroll-View就行,另一个预制体为Content中的内容,这里我是用了button
然后在脚本中分别获取Scroll-View中的content以及button预制体的引用
[SerializeField] private GameObject Content;
[SerializeField] private GameObject Mail_ItemPrefab;
2.Items列表
创建一个列表用来存放每个items
private List<GameObject> items = new List<GameObject>();
3.创建item并且加入items表
根据指定的预制体创建一个item对象,这里是按钮,定义了下点击回调以及text内容,然后把它加入items表中
private void CreateItems(string data)
{
GameObject item = Instantiate(Mail_ItemPrefab, Content.transform);
item.SetActive(true);
Button itemButton = item.GetComponent<Button>();
if (itemButton != null)
{
TextMeshProUGUI itemText = itemButton.GetComponentInChildren<TextMeshProUGUI>();
if (itemText != null)
{
itemText.text = data;
}
itemButton.onClick.AddListener(() => { Debug.Log("点击了: " + data); });
}
items.Add(item);
}
4.动态加载内容
为每一个邮件数据创建一个对象
public void LoadContent(List<string> dataList)
{
ClearItems();
foreach (string data in dataList)
{
CreateItems(data);
}
}
5.清除当前所有内容
private void ClearItems()
{
foreach(GameObject item in items)
{
Destroy(item);
}
items.Clear();
}
6.滚动窗口设置
创建一个Scroll-View后要实现垂直列表布局滚动我的设置如下
Scroll Rect中把滚动条都设置为None,Content中首先加入一个Grid Layout Group控制布局,然后加入Content Size Fitter进行适应(这个不加的话会导致滚动后又弹回最顶端)

7.关闭UI
这里尝试了下常见的点击框外界面就会关闭UI的方法,具体就是在这层UI的底部放一个全屏的透明按钮,按钮回调就是向对象池归还这个UI
private GameObject currentUI;
void Start()
{
currentUI = gameObject;
if (existAreaBtn != null)
{
existAreaBtn.onClick.AddListener(On_Exist_AreaBtn_Clicked);
}
}
private void On_Exist_AreaBtn_Clicked()
{
UIPool_Manager.Instance.ReturnToPool("Mail", currentUI);
}
用户设置存储
用于保存设置用户的设置,比如背景音乐大小等,由于都是些简短的数据,要么是一个float或者bool,或者string,因此用轻量的PlayerPrefs来进行存储
1.创建设置UI于滑动条
就基本和上面的邮箱的步骤是一样的,创建UI然后非UI区域推出按钮,之后在UI页面中创建一个slider滑动条
2.读取设置信息并且更新给UI
在UI的Start中进行目标数据的读取并且赋值给Slider
首先在脚本内获取Slider的引用
[SerializeField]Slider Volume_Slider;
在Start中先判断是否在PlayerPrefs中含有想要的数,如果没有则默认为1.0,有则读取并且也将Slider的值设置为对应的值
void Start()
{
if (PlayerPrefs.HasKey("Volume_Value"))
{
Debug.Log("find Volume");
Volume_Slider.value = PlayerPrefs.GetFloat("Volume_Value");
}
else
{
Debug.Log("not find Volume");
Volume_Slider.value = 1.0f;
PlayerPrefs.SetFloat("Volume_Value");
}
}
3.用户修改设置
Slider滑动条回调的绑定,由于本脚本是挂载在SettingUI上的,因此在Slider的组件面板中将此UI预制体拖入并且选择对应的回调函数
public void OnVolume_Value_Changed(float Value)
{
PlayerPrefs.SetFloat("Volume_Value", Value);
PlayerPrefs.Save();
}
明日计划:
基础的背包页面与头像选择页面

6297

被折叠的 条评论
为什么被折叠?



