unity新手开发学习日志Day1

今天学习一下主界面的通用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();
    }

明日计划:

基础的背包页面与头像选择页面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值