[Unity优化]对象池

对象池,简单的来说,就是当你想GameObject消失时,而这个GameObject又需要经常用到时,你就不必去Destroy掉,而是隐藏掉,为了便于管理,就把这些GameObject统一放在一个“池”中。许多时候,实例化一个物体需要消耗一定的性能,不直接Destroy掉就会提高性能。


下面实现的对象池,是针对Resources.Load来设计的。

1.根据Resources下的预制物生成配置文件

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using System.IO;

//生成配置信息,供对象池使用
public class PoolConfigure {

    private static List allFiles;

	//右键菜单  
    [MenuItem("Assets/Build Pool Configure")]
    static void Build()
    {
        string path = AssetDatabase.GetAssetPath(Selection.objects[0]);
        if (!path.Contains("Resources"))
        {
            Debug.Log("请选中Resources文件夹");
            return;
        }

        path = Application.dataPath + path;
        path = path.Replace("AssetsAssets", "Assets");
        Debug.Log(path);

        allFiles = new List();
        GetAllFiles(path);
        allFiles.RemoveAll((s) => { return s.Contains("meta");});
        //for (int i = 0; i < allFiles.Count; i++)
        //{
        //    Debug.Log(allFiles[i]);
        //}

        //对路径进行处理,使其可以直接通过Resources.Load进行加载
        for (int i = 0; i < allFiles.Count; i++)
        {
            allFiles[i] = allFiles[i].Replace("\\", "/");
            allFiles[i] = allFiles[i].Substring(allFiles[i].IndexOf("Resources/") + 10);
            allFiles[i] = allFiles[i].Replace(".prefab", "");
            Debug.Log(allFiles[i]);
        }

        //生成配置文件
        string outputPath = Application.dataPath + "/Script/ObjectPool/PoolInfo.cs";

        ClassTemplate.Start(outputPath);

        ClassTemplate.WriteClass("");
        for (int i = 0; i < allFiles.Count; i++)
        {
            string name = allFiles[i].Substring(allFiles[i].LastIndexOf('/') + 1);
            ClassTemplate.WriteField("string", name, "public const", "\"" + allFiles[i] + "\"");
        }
        
        ClassTemplate.End();
        AssetDatabase.Refresh();
    }

    //获取一个根目录下的所有文件
    public static void GetAllFiles(string dir)
    {
        string[] files = Directory.GetFiles(dir);
        foreach (var item in files)
        {
            allFiles.Add(item);
        }

        string[] dirs = Directory.GetDirectories(dir);
        foreach (var item in dirs)
        {
            GetAllFiles(item);
        }
    }

}

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;

//创建类模板(文件)
//统一在语句末加上"\n"
public class ClassTemplate {

    private static StringBuilder builder;
    private static string path;
    private static string className;
    private static bool useNameSpace = false;

    private static string fourSpace = "    ";

    public static void Start(string savaPath)
    {
        builder = new StringBuilder();
        path = savaPath;
        className = savaPath.Substring(savaPath.LastIndexOf("/") + 1);
        className = className.Substring(0, className.IndexOf('.'));
        useNameSpace = false;
        //Debug.Log("className:" + className);
    }

    public static void End()
    {
        if (useNameSpace) Write(fourSpace + "}\n}");
        else Write("\n}");
        File.WriteAllText(path, builder.ToString());
    }

    public static void WriteUsing(string s)
    {
        Write("using " + s + ";" + "\n");
    }

    public static void WriteNameSpace(string s)
    {
        useNameSpace = true;
        Write("\nnamespace " + s + "\n{\n");
    }

    public static void WriteClass(string parentName)
    {
        if (!useNameSpace) Write("\n");
        WriteZeroOrOneInterval();
        Write("public class " + className);
        if (!string.IsNullOrEmpty(parentName)) Write(" : " + parentName + " ");
        else Write(" ");
        WriteZeroOrOneInterval();
        Write("{\n\n");
    }

    //prefix即变量修饰符,如public static 
    public static void WriteField(string type, string name, string prefix = "public", string value = "")
    {
        WriteOneOrTwoInterval();
        if (string.IsNullOrEmpty(value)) Write(prefix + " " + type + " " + name + ";\n");
        else Write(prefix + " " + type + " " + name + " = " + value + ";\n");
    }

    /// 
    /// methodInfo:如public static void Init()
    /// 
    /// 
    public static void StartMethod(string methodInfo)
    {
        Write("\n");
        WriteOneOrTwoInterval();
        Write(methodInfo);
        Write("\n");
        WriteOneOrTwoInterval();
        Write("{\n");
    }

    public static void MethodProcess(string s)
    {
        WriteTwoOrThreeInterval();
        Write(s + "\n");
    }

    public static void EndMethod()
    {
        WriteOneOrTwoInterval();
        Write("}\n");
    }

    public static void WriteConstructionRead(List name, List readMethod)
    {
        Write("\n");
        WriteOneOrTwoInterval();
        Write("public " + className + "()\n");
        WriteOneOrTwoInterval();
        Write("{\n");

        for (int i = 0; i < name.Count; i++)
        {
            WriteTwoOrThreeInterval();
            Write(name[i] + " = " + readMethod[i] + "();\n");
        }

        WriteOneOrTwoInterval();
        Write("}\n\n");
    }

    public static void WriteConstructionCopy(List name, List readMethod)
    {
        Write("\n");
        WriteOneOrTwoInterval();
        Write("public " + className + "(" + className + " a" + ")\n");
        WriteOneOrTwoInterval();
        Write("{\n");

        for (int i = 0; i < name.Count; i++)
        {
            if (readMethod[i].Contains("List"))
            {
                string type = readMethod[i].Substring(4, readMethod[i].Length - 8).ToLower();
                //Debug.Log(type);
                WriteTwoOrThreeInterval();
                Write(name[i] + " = new " + type + "[a." + name[i] + ".Length];\n");
                WriteTwoOrThreeInterval();
                Write("Array.Copy(a." + name[i] + ", " + name[i] + ", " + "a." + name[i] + ".Length);\n");
            }
            else if (readMethod[i].Contains("Dict"))
            {
                string type = readMethod[i].Substring(4, readMethod[i].Length - 8).ToLower();
                //Debug.Log(type);
                WriteTwoOrThreeInterval();
                Write(name[i] + " = " + "new Dictionary(a." + name[i] + ");\n");
            }
            else
            {
                WriteTwoOrThreeInterval();
                Write(name[i] + " = " + "a." + name[i] + ";\n");
            }
        }

        WriteOneOrTwoInterval();
        Write("}\n\n");
    }

    public static string GetClassName()
    {
        return className;
    }

    public static void Write(string s)
    {
        builder.Append(s);
    }

    private static void WriteZeroOrOneInterval()
    {
        if (useNameSpace) Write(fourSpace);
    }

    private static void WriteOneOrTwoInterval()
    {
        if (useNameSpace) Write(fourSpace + fourSpace);
        else Write(fourSpace);
    }

    private static void WriteTwoOrThreeInterval()
    {
        if (useNameSpace) Write(fourSpace + fourSpace + fourSpace);
        else Write(fourSpace + fourSpace);
    }

}

那么对于下图:

[Unity优化]对象池_第1张图片

生成的配置文件如下:

public class PoolInfo {

    public const string Cube = "Character/Enemy/Cube";
    public const string Sphere = "Character/Hero/Sphere";
    public const string Capsule = "Effect/Capsule";

}



2.对象池

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

public class PoolManager : CSharpSingletion {

    //path -- go
    public Dictionary> dic = new Dictionary>();

    //把对象放到对象池中
    public void SetPoolObject(string path, GameObject go)
    {
        if (!dic.ContainsKey(path))
        {
            List list = new List();
            list.Add(go);
            dic.Add(path, list);
        }
        else
        {
            dic[path].Add(go);
        }
        go.SetActive(false);
    }

    //从对象池中取出对象
    //isNewGo为true,表示返回新的go,否则表示返回缓存池中的go
    public GameObject GetPoolObject(string path, bool isNewGo = false)
    {
        //Resources.Load: 重复Load同样的资源只会指向同一段内存,不会增加开销
        if (isNewGo)
        {
            GameObject go = MonoBehaviour.Instantiate(Resources.Load(path));
            return go;
        }

        if (!dic.ContainsKey(path))
        {
            GameObject go = MonoBehaviour.Instantiate(Resources.Load(path));
            return go;
        }
        else
        {
            if (dic[path].Count > 0)//还未取完
            {
                GameObject go = dic[path][0];
                go.SetActive(true);
                dic[path].Remove(go);
                return go;
            }
            else//已经取完
            {
                GameObject go = MonoBehaviour.Instantiate(Resources.Load(path));
                return go;
            }
        }
    }

    public void Cache(string path, int amount = 1, bool isNewGo = true)
    {
        for (int i = 0; i < amount; i++)
        {
            GameObject go = GetPoolObject(path, isNewGo);
            SetPoolObject(path, go);
        }
    }

}

3.测试

using UnityEngine;
using System.Collections;

public class PoolTest : MonoBehaviour {

	void Start () 
    {
        //1
        //PoolManager.Instance.Cache(PoolInfo.Cube, 5000, true);
        //Profiler.BeginSample("PoolTest");
        //for (int i = 0; i < 5000; i++)
        //{
        //    PoolManager.Instance.GetPoolObject(PoolInfo.Cube);
        //}
        //Profiler.EndSample();

        //2
        Profiler.BeginSample("PoolTest");
        for (int i = 0; i < 5000; i++)
        {
            Instantiate(Resources.Load(PoolInfo.Cube));
        }
        Profiler.EndSample();
	}
	
}

结论:通过查看Profiler,以我的本本的性能来看,不使用对象池的耗时,是使用对象池的耗时的两倍多。如果是以人物模型这种多点多面的模型为测试对象,效果也就更明显鸟!

你可能感兴趣的:(Unity优化)