2018-04-23 23:27:22 ghl1390490928 阅读数 496

      其实一直很想写博客,但总是以为自己没有时间,今天和女友在学校图书馆看书,无意看到一个技术大牛作的序,从大学开始坚持写技术博客,把自己的所想所获记录下来,现在觉得确实是一种自我提高的有效方式,所以今天终于痛下决心,刚从健身房回到宿舍,便静下心来思忖自己的第一篇博客。由于最近在摸索Unity3D的游戏开发,在学习制作一款FPS射击游戏时,接触到了缓存池(也称对象池)的使用,今天就把自己的所想所获与大家分享。

      至于为什么会有缓存池这种东西存在,自然是以节省内存开销的目的。说通俗点,缓存池技术一般应用于实例化物体的过程中,Unity一般实例化物体会使用Instantiate()函数,销毁游戏物体一般使用Destroy()函数,频繁使用这两个函数会造成较大的内存占用,这时有一种技术,假设有一个水池,里边提前存放好游戏要用到的物体,当游戏不需要时,这些物体隐藏在水池里,当游戏需要时,这些物体就显示出来,这就是缓存池技术。

      Asset Store中提供了多种缓存池插件,个人用到的时Pool Boss,感觉还是比较好用的。当然Pool Boss集成在Core GameKit插件中,售价是25$,作为一个学生党仍然是感觉比较贵的,因此想办法单独下载了Pool Boss来使用,个人使用的版本是1.1.4,当然各位大佬完全可以选择Asset Store提供的插件,本人也很想知道那个版本有什么强大的功能。

        在我所学习制作的FPS射击游戏中,在当主角枪支发射的射线与敌人挂载的碰撞体发生接触,触发粒子特效时,这里用到了缓存池技术。将Pool Boss包导入,把PoolBoss预制体拖曳到Hierarchy层级面板,这样在Inspector视图里可以看到其所搭载的插件,把需要缓存的粒子预制体拖入黄色方框,其中Preload Qty决定了缓存的粒子数目,Allow Instantiate More也可以勾选,表示需要缓存的最大粒子数目,这个地方可以自己设置。这样在程序运行时可以实时监测粒子的产生和回收数目。



        相关代码如下:

       (1)把Instantiate()函数替换成:

                      DarkTonic.PoolBoss.PossBoss.Spawn("FX",my_transform.position,my_transform.rotation,null);

      (2) 把Destroy()函数替换成:

                DarkTonic.PoolBoss.PoolBoss.Despawn(this.transform);

        另外不足:

         自己在对粒子和子弹这类物体进行测试,效果良好,但是在敌人的产生测试中,效果不理想,缓存池没有进行有效回收,暂时还不知道主要原因是什么;还有一种动态加载的技术,本人还没有深入研究。

          自己的第一篇技术博客就这样诞生了,感谢女友激发的灵感,立个flag:争取每两天更新一下,一段时间后看看自己的成长。当然其中自然有很多不足之处,还请各位大牛批评指正,小生一定虚心接受教诲。



      

2016-12-09 20:40:00 u013287454 阅读数 64

在公司看源码的时候,发现一个投篮的场景中,玩家把篮球不断投出去,然后不断的创建和销毁对象。于是试着写个缓存池来管理。

有个叫PoolManager的工具类插件可以很方便的实现,可以看这里的介绍Unity3D研究院之初探PoolManager插件。

但是其实对象池就是预先创建一点对象,当我们需要用的时候,去拿就行了。如果没有,再创建。使用完毕后也并不销毁,方便下次使用。有点类似于Android中的listview的holder。看起来并不难,花了点时间写了个。

BufferPoolList.cs

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

public class BufferPoolList{

    private List<GameObject> pool;
    private GameObject prefab;
    private Transform prefabParent;

    
    public BufferPoolList(GameObject obj, Transform parent, int count)
    {
        prefab = obj;

        pool = new List<GameObject>(count);
        prefabParent = parent;

        for (int i = 0; i < count; i++)
        {
            GameObject objClone = GameObject.Instantiate(prefab) as GameObject;
            objClone.transform.parent = prefabParent;//为克隆出来的子弹指定父物体
            objClone.name = "Clone0" + i.ToString();
            objClone.SetActive(false);
            pool.Add(objClone); 
        }
    }


    public GameObject GetObject()
    {
        //遍历缓存池 找空闲的物体
        foreach (GameObject iter in pool)
        {
            if (iter.activeSelf == false)
            {
                iter.transform.SetParent(prefabParent);
                iter.SetActive(true);
                return iter;
            }

        }
        GameObject newPrefab = GameObject.Instantiate(prefab) as GameObject;
        newPrefab.transform.SetParent(prefabParent);
        newPrefab.name = "Clone0" + pool.Count.ToString();
        newPrefab.SetActive(true);
        pool.Add(newPrefab);
        return newPrefab;
    }
}

在Player上的脚本或者控制类脚本上初始化之后,需要使用我们初始化的物体时,只需要GetObject()即可,用完将物体设置不可见即可。可以在实例化的物体上判断物体是否超出屏幕边界,超出 则 .SetActive(false);设置物体不可见。

这样写虽然不用每次都创建对象,缓存池的目的算是达到了,但是每次获取可用对象时都去做个循环,总感觉怪怪的。所以换种写法,不用list,改用 Queue 队列来写。代码如下:

BufferPool.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class BufferPool
{
    private Queue<GameObject> pool;
    private GameObject prefab;
    private Transform prefabParent; 

    //使用构造函数构造对象池
    public BufferPool(GameObject obj,Transform parent,int count)
    {
        prefab = obj;
        
        pool = new Queue<GameObject>(count);
        prefabParent = parent;

        for (int i = 0; i < count; i++)
        {
            GameObject objClone = GameObject.Instantiate(prefab) as GameObject;
            objClone.transform.parent = prefabParent;//为克隆出来的子弹指定父物体
            objClone.name = "Clone0" + i.ToString();
            objClone.SetActive(false);
            pool.Enqueue(objClone);  
        }
    }

    
    public GameObject GetObject()
    {
        GameObject obj = null;

        if (pool.Count > 0)
        {
            obj = pool.Dequeue();  //Dequeue()方法 移除并返回位于 Queue 开始处的对象
            obj.transform.position = prefabParent.position;
        }
        else
        {
            obj = GameObject.Instantiate(prefab) as GameObject;
            obj.transform.SetParent(prefabParent);
           
        }
        
        obj.SetActive(true);
        return obj;
    }

    //回收对象
    public void Recycle(GameObject obj)
    {
        obj.SetActive(false);
        pool.Enqueue(obj);//加入队列
    }
}

这样获取对象时,就不用做循环了。每次使用时,出列。实例化的物体不再使用时,再让他加入队列。这样也有个不方便的地方,需要在游戏物体的脚本上,拿到 Player上的 BufferPool.cs 脚本 ,来回收对象,因为获取对象时,已经把该实例给移出队列了,所以当不再使用时,必须调用Recycle方法来将其加入到队列中去。

虽然一个用的List ,一个用的 Queue,但是道理是一样的。目的也都是为了解决Unity实例化对象慢的问题。

链接:http://539go.com/2016/11/10/Unity-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B9%8B%E5%86%99%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E7%BC%93%E5%AD%98%E6%B1%A0/

2019-08-21 10:19:25 Mr_DODODODO 阅读数 44

知识点
    Dictionary,List,GameObject

作用
    节约性能,减少cpu和内存消耗

缓存对象在层级窗口的结构
     

池子容器类—PoolData.cs

/// <summary>
/// 池子数据
/// </summary>
public class PoolData
{
    //池子中存储对象的父对象,当对用不使用时 会做为该对象的子对象(目的是让层级面板看起来不那么乱)
    private GameObject root;
    //池子列表容器,用于存储不用的对象
    private List<GameObject> listObjs;

    /// <summary>
    /// 初始化单一池子 将池子父对象传入 新建一个对象作为它的子对象
    /// </summary>
    /// <param name="str"></param>
    public PoolData(GameObject poolRoot, string str)
    {
        //新建一个池子父对象
        root = new GameObject(str);
        //将它的父对象设置为 池子跟对象
        root.transform.SetParent(poolRoot.transform);
        //新建容器
        listObjs = new List<GameObject>();
    }

    /// <summary>
    /// 是否存在对象
    /// </summary>
    public bool IsHave
    {
        get
        {
            return listObjs.Count > 0;
        }
    }

    /// <summary>
    /// 从对象列表中取出对象
    /// </summary>
    /// <returns></returns>
    public GameObject Pop()
    {
        //取出第一个对象
        GameObject obj = listObjs[0];
        //并且从存储他的容器中取出去
        listObjs.RemoveAt(0);
        //激活它
        obj.SetActive(true);
        //解出父子关系
        obj.transform.SetParent(null);
        return obj;
    }

    /// <summary>
    /// 向列表中压入对象
    /// </summary>
    /// <param name="obj"></param>
    public void Push(GameObject obj)
    {
        //设置失活
        obj.SetActive(false);
        //将其放到池子父对象下
        obj.transform.SetParent(root.transform);
        //记录它
        listObjs.Add(obj);
    }
}

池子管理类—PoolMgr.cs

/// <summary>
/// 缓存池管理器
/// </summary>
public class PoolMgr : BaseManager<PoolMgr>
{
    //池子根容器对象
    private GameObject root = null;
    //池子数据容器 key作为池子名 value作为池子中的数据
    private Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();

    /// <summary>
    /// 压入缓存池
    /// </summary>
    public void Push(string poolName, GameObject obj)
    {
        //如果没有池子跟对象 新建一个 (目的让层级窗口不乱)
        if (root == null)
            root = new GameObject("Pool");
        //有池子 直接往里面压
        if ( poolDic.ContainsKey(poolName) )
            poolDic[poolName].Push(obj);
        //没有池子
        else
        {
            //新建一个池子数据类
            PoolData data = new PoolData(root, poolName);
            //将子对象压入 会跳转到PoolData的Push方法
            data.Push(obj);
            //将数据存到数据容器字典中
            poolDic.Add(poolName, data);
        }
    }

    /// <summary>
    /// 弹出对象
    /// </summary>
    /// <param name="poolName">对象池子名</param>
    /// <param name="LoadOver">加载出来后用来做什么的回调函数</param>
    public void Pop(string poolName, Action<object> LoadOver = null)
    {
        GameObject obj = null;
        //池中存在
        if (poolDic.ContainsKey(poolName) && poolDic[poolName].IsHave)
        {
            //直接跳转到PoolData的Pop方法
            obj = poolDic[poolName].Pop();
            //因为采用的异步加载 这里已经有对象 不用加载 直接调用回调
            if(LoadOver != null)
                LoadOver(obj);
        }
        //池中不存在 异步加载资源 请查看Resources资源加载模块
        else
            ResourcesMgr.Instance.LoadRes(poolName, LoadOver);
    }

    /// <summary>
    /// 清除缓存池 一般会在加载场景时清空 因为过场景 缓存池对象一般会全部清空
    /// </summary>
    public void Clear()
    {
        //跟对象置空
        root = null;
        //容器清理
        poolDic.Clear();
    }
}

 

2015-07-24 11:24:28 hfreedomx 阅读数 1780

首先小生自己庆祝一下上周小生的签证申请顺利通过了,第一次是自己做自己的担保人成功申请了签证。实在是非常的高兴。微笑外加最近马上Swift2.0就要发布了,小生最近一直都在做一款关于汇率的App,用的就是Swift来开发的,真不知升级到IOS9之后是不是需要进行一番很大的改动(改动在所难免,但是知识需要与时具进嘛,加油!)。同样引申出来Unity不知道又要有什么变化了,虽说现在的系统几乎就是对软件支持向下兼容的,但是为了使用新的Api还有很多被替换掉的老的Api的发生,我想不管是原生App还是Unity的App都要进行一次脱胎换骨的洗礼吧。


小生在这两个方面还只是新手,所以对于小生这种初出茅庐的小菜鸟来说真是要有的忙了,但是毕竟这些都是后话了,现在Unity4还没用明白呢,还是赶快把眼前的知识打牢才是正经事儿不是,所以就让我们开始上周博客所谈及的话题吧-----Asset Bundle的缓存利用篇!(小生电脑也是有Unity5的,苦于只有4的Pro的license,将来有Unity5的license的时候对现在的知识进行一下升级的,还请各位看官多多包涵委屈


小生研究了一下关于使用缓存来保存下载后的AssetBundle的方法,发现缓存利用与否,打包的方式都是一样的(如果有看官还想了解下打包的知识,请参照小生的[打包篇])。正常如果只是下载文件而不进行保存的话是使用[new WWW(url)]来实现,而实用缓存的话就要利用另外一个方法了,那就是[WWW.LoadFromCacheOrDownload]这个方法了,要注意一点的是,这个方法不需要实例化,因为它是个静态方法。各位看官如果感兴趣的话,可以参照上一篇文章[基础打牢篇]中小生加入的代码片。


多余不说,先上一下总体的代码给各位看官!

using UnityEngine;
using System.Collections;

public class GameController : MonoBehaviour {

	private string assetBundleUrl = "http://127.0.0.1/AssetBundle/4/";

	// Use this for initialization
	void Start () {
		StartCoroutine(Download("Ball", 0));
	}
	
	// Update is called once per frame
	void Update () {
	
	}

	private IEnumerator Download(string assetBundleName, int version) {
		string filePath = assetBundleUrl + assetBundleName + ".unity3d";

		//Check the asset bundle file with version whether exist in cache of client
		if (Caching.IsVersionCached (filePath, version)) {
			Debug.Log("The file : " + filePath + "with version : " + version.ToString() + " is exist!");
		}
		else {
			Debug.Log("The file : " + filePath + "with version : " + version.ToString() + " is not exist!");
		}

		using (WWW www = WWW.LoadFromCacheOrDownload(filePath, version)) {
			yield return www;

			//Check the game object whether exist in asset bundle file 
			if (www.assetBundle.Contains(assetBundleName)) {
				GameObject gObject = www.assetBundle.mainAsset as GameObject;
				Instantiate(gObject);
			}
			else {
				Debug.Log("The game object : " + assetBundleName + "is not exist in asset bundle file!");
			}
			www.assetBundle.Unload(false);
		}
	}
}

从上面代码片可以看出,这里的要比之前不利用缓存的地方多了个version的整型参数,对,就是主要需要这个参数哦!这个version的参数就是起到了来判断是否需要重新下载的作用。简单的说,打包的时候是没有定义version的地方的,所以这个地方你需要自定义传入一个整数变量,比如说第一次下载的时候你给version传入0的话,如果你更新了服务器端的资源包后,但是你重新启动项目后如果没给这个version更新的话(比如说还是0)。这种情况下,客户端会认为关于这个资源包是没有更新的会直接去读取客户端缓存中的这个文件(如果缓存中这个文件不存在的话会重新下载)。反之你给version定义0以外的值的话,那就会重新下载这个asset bundle喽(前提是你定义的这个version值之前没有下载过)。


这样,缓存下载的Asset Bundle就这样简单的搞定喽!可能各位看官发现小生的代码片中还包含了其他的代码。嘿嘿,这个就留作预习之用喽,对于缓存利用的更多的知识小生会在下一篇的缓存利用进阶篇中和大家继续讨论(主要小生比较喜欢循序渐进地学习方式,所以还请各位看官多多包涵拍砖噢微笑)。



没有更多推荐了,返回首页