Unity3D程序基础框架(上)

前言:

最近在B站看到了唐老师出的课程——学习Unity3D基础框架,我觉得对我会很有用,特写此文章做以记录。

这篇需要你了解一些C#的知识。


单例模式基类模块

我们创建一个新项目,创建标准的目录结构

在Base下面新建脚本——BaseManager.cs

单例模式基类模块的作用主要是——减少单例模式重复代码的书写

我们都知道单例模式是怎么一种设计模式,那么什么是单例模式基类模块呢?其实就是单例模式的基础模板。

代码奉上

public class BaseManager<T> where T:new()
{
    private static T _instance;
    public static T GetInstance() {
        if (_instance == null) {
            _instance = new T();
        }
        return _instance;
    }
}

之后只要项目中需要用到单例模式,只要继承此类即可。

public class BaseManager<T> where T:new() 这一句后面的部分可能很多朋友不了解,这是一个C#中泛型的知识点——泛型约束。

关于泛型约束的概念可以参考这篇文章


缓存池模块

基本原理与实现

缓存池模块主要是为了节约性能,减少CPU和内存消耗。

Unity每一次创建对象,实际都是C#在内存中申请了一块空间,之后在Unity场景中Destroy这个物体对象,实际上只是断开了实例化对象对于内存的那块空间的一个引用,内存中依然存在那个对象对应的空间。

直到内存占用满了,CPU才会回过头来找内存中没有被引用的空间(一次GC),然后释放该空间,然后建立新的对象,如此往复。

这个GC步骤是比较消耗CPU的,所以在比较不好的机器上可能造成游戏的卡顿,所以,我们需要缓存池。

缓存池可以将你创建的对象在你用完后收录起来,等到你再要调用的时候,就可以直接调用缓存池中收录的对象,如此往复,形成闭环。不必再去申请新的内存。

我们在ProjectBase目录下创建一个新目录——Pool,用来存放缓存池脚本。

public class PoolMgr : BaseManager<PoolMgr>
{
    //这里是缓存池模块

    //创建字段存储容器
    public Dictionary<string,List<GameObject>> pool1Dic
        =new Dictionary<string, List<GameObject>>();


    //取得游戏物体
    public GameObject GetObj(string name) {
        GameObject obj = null;
        if (pool1Dic.ContainsKey(name) && pool1Dic[name].Count > 0)
        {
            //取得List中的第一个
            obj = pool1Dic[name][0];
            //移除第零个(这样才能允许同时创建多个物体)
            pool1Dic[name].RemoveAt(0); 
        }
        else {
            //缓存池中没有该物体,我们去目录中加载
            //外面传一个预设体的路径和名字,我内部就去加载它
            obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
            //创建对象后,将对象的名字与池中名字相符
            obj.name = name;
        }
        //让物体显示出来
        obj.SetActive(true);
        return obj;
    }

    //外界返还游戏物体
    public void PushObj(string name,GameObject obj) {
        //让物体失活
        obj.SetActive(false);
        //里面有记录这个键
        if (pool1Dic.ContainsKey(name))
        {
            pool1Dic[name].Add(obj);
        }
        //未曾记录这个键
        else {
            pool1Dic.Add(name, new List<GameObject>() { obj});
        }
    }
}

我们来做个试验,给一个场景创建这样的脚本,实现点击鼠标创建物体

public class test : MonoBehaviour
{
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) {
            //向缓存池中拿东西
            PoolMgr.GetInstance().GetObj("Cube");
        }
        if (Input.GetMouseButtonDown(1))
        {
            PoolMgr.GetInstance().GetObj("Sphere");
        }
    }
}

物体身上挂着另一个脚本

public class DelayPush : MonoBehaviour
{
    void  OnEnable()
    {
        Invoke("Push", 1);
    }

    void Push() {
        PoolMgr.GetInstance().PushObj(transform.name, this.gameObject);
    }
}

最终效果

这样创造新物体实际就是唤醒旧物体,虽然好像占用了内存,但是却为CPU有很大好处,减少了GC,增加了游戏体验感。

优化

规范化

为了使资源管理更加工整,我们考虑到应该让都存放在一个空物体下,这样会非常便利我们的管理。

public class PoolMgr : BaseManager<PoolMgr>
{
    //这里是缓存池模块

    //创建字段存储容器
    public Dictionary<string,List<GameObject>> pool1Dic
        =new Dictionary<string, List<GameObject>>();

    private GameObject poolObj;

    //取得游戏物体
    public GameObject GetObj(string name) {
        GameObject obj = null;
        if (pool1Dic.ContainsKey(name) && pool1Dic[name].Count > 0)
        {
            //取得List中的第一个
            obj = pool1Dic[name][0];
            //移除第零个(这样才能允许同时创建多个物体)
            pool1Dic[name].RemoveAt(0); 
        }
        else {
            //缓存池中没有该物体,我们去目录中加载
            //外面传一个预设体的路径和名字,我内部就去加载它
            obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
            //创建对象后,将对象的名字与池中名字相符
            obj.name = name;
        }
        //让物体显示出来
        obj.SetActive(true);

        //断开了缓存池物体与poolObj的父子关系
        obj.transform.parent = null;

        return obj;
    }

    //外界返还游戏物体
    public void PushObj(string name,GameObject obj) {
        if (poolObj == null)
        {
            poolObj = new GameObject("Pool");

        }
        //将这个物体设置为空物体
        obj.transform.parent = poolObj.transform;



        //让物体失活
        obj.SetActive(false);
        //里面有记录这个键
        if (pool1Dic.ContainsKey(name))
        {
            pool1Dic[name].Add(obj);
        }
        //未曾记录这个键
        else {
            pool1Dic.Add(name, new List<GameObject>() { obj});
        }
    }
}

这样,就可以实现所有申请的对象都放在Pool这个空物体下面,当某个对象物体被激活才会回到主目录下。

场景跳转问题

还有一个问题,当场景切换时,缓存池的对象物体都会被销毁,但是引用还存在,这会占用内存又没有用处,

我们可以给PoolMgr添加一个清空方法来应对场景转换的情况。

    //清空缓存池的方法
    //主要用在场景切换时
    public void Clear() {
        pool1Dic.Clear();
        poolObj = null;
    }

这样,跳转场景之前调用这个Clear方法就好了。

更细节的规范化

我们现在虽然可以实现生成的缓存对象全部在Pool这个空物体下,但是却没有明确的划分,如果各式各样的物体很多,就会很杂乱。

所以我们可以修改代码对他们进行分类。

思路是这样的:我们创建了一个字典来保存记录缓存对象,键是字符串、值是Gameobject的集合,我们可以将值替换成一个新的类型——PoolData。

下面是最终的PoolMgr.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//抽屉数据,池子中的一列容器
public class PoolData
{
    //抽屉中,对象挂载的父节点
    public GameObject fatherObj;
    //对象的容器
    public List<GameObject> poolList;

    public PoolData(GameObject obj, GameObject poolObj)
    {
        //根据obj创建一个同名父类空物体,它的父物体为总Pool空物体
        fatherObj = new GameObject(obj.name);
        fatherObj.transform.parent = poolObj.transform;

        poolList =  new List<GameObject>() {  };

        PushObj(obj);
    }

    //像抽屉里面压东西并且设置好父对象
    public void PushObj(GameObject obj)
    {
        //存起来
        poolList.Add
  • 25
    点赞
  • 119
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

轻舟在过

您的支持是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值