0评论

Unity性能优化之代码优化小技巧_018

萧然 2018-09-09 1.2k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏开发行业精英群711501594

参与“Unity性能优化”征文活动

对于Unity性能优化,目前接触到的大概有这几个方面:
1. Draw Call
2. 资源(模型、贴图、粒子
3. 渲染(相机、光照、Shader)
4. 网络
5. 代码(代码编写、资源加载、物理系统)
可以在Unity自带的Profiler窗口查看项目性能消耗主要在哪几个地方,然后有针对性的进行优化。

作为一个程序,这里跟大家分享一下最近常用到代码方面的一点技巧,如有不足之处,欢迎大大们提出宝贵意见~

1. 在场景中有大量物体频繁的激活或隐藏时,不使用SetActive(),在需要隐藏时移到屏幕外 ,显示时再移到屏幕内,即修改transform.position。还有一个方法,把需要隐藏的物体设为一个已隐藏的物体的子物体,因为父物体是未激活状态,子物体会自动隐藏,不过这种方法消耗与SetActive()差不多,不推荐使用。
下面是测试代码:
    void Start()
    {
        //准备工作
        List<GameObject> objs = new List<GameObject>();
        DateTime begainTime;
        //生成预设GameObject, 空物体或简单的GameObject,性能消耗较小,GameObject越复杂,消耗越大
        GameObject prefab = GameObject.CreatePrimitive(PrimitiveType.Cube);
        for (int i = 0; i < 100; i++)
        {
            GameObject.CreatePrimitive(PrimitiveType.Cube).transform.parent = prefab.transform;
        }
        //加载一定数量GameObject到场景, 大量操作才会对性能产生明显影响,GameObject数量越多,消耗越大
        for (int i = 0; i < 100; i++)
        {
            objs.Add(Instantiate(prefab, transform));
        }
        //开始测试
        begainTime = DateTime.Now;
        for (int i = 0; i < objs.Count; i++)
        {
            objs[i].SetActive(false);
        }
  Debug.Log("SetActive(false)  " + (DateTime.Now - begainTime).ToString());
        begainTime = DateTime.Now;
        for (int i = 0; i < objs.Count; i++)
        {
            objs[i].SetActive(true);
        }
  Debug.Log("SetActive(true)  " + (DateTime.Now - begainTime).ToString());
        begainTime = DateTime.Now;
        for (int i = 0; i < objs.Count; i++)
        {
            objs[i].transform.position = Vector3.one * 1000;
        }
Debug.Log("transform.position  " + (DateTime.Now - begainTime).ToString());
        Transform idleParent = new GameObject().transform;
        idleParent.gameObject.SetActive(false);
        begainTime = DateTime.Now;
        for (int i = 0; i < objs.Count; i++)
        {
            objs[i].transform.parent = idleParent;
        }
  Debug.Log("transform.parent  " + (DateTime.Now - begainTime).ToString());
    }
输出结果:
SetActive(true)消耗最大,SetActive(false)与transform.parent其次,transform.position消耗最小,占用的时间可以忽略不计。

2. 动态实例化到场景的物体,名字都会有一个后缀(Clone),有时候为了方便识别,会修改其名字,同样会产生性能消耗。比如:
        //修改名字, 字符串操作
        for (int i = 0; i < objs.Count; i++)
        {
            objs[i].name = "New Obj " + i;
        }

3. 在不影响正常运行结果的情况下,减少Update或FixUpdate的调用次数
假设现在将所有的Updata刷新逻辑写在DoUpdata中
 void DoUpdata() { Debug.Log("Do Updata"); }

3.1 每隔一定数量帧,执行一次DoUpdata
    int spaceCount = 10;
    int updateCount;
    void Update()
    {
        if (updateCount % spaceCount == 0)
        {
            updateCount = 0;
            //DoUpdata();
        }
        updateCount++;
    }

3.2 使用协程While(true)循环,每次循环间隔一定时间,调用DoUpdata,需要在Start函数中开启协程
   float spaceTime = 0.1f;
    IEnumerator StartUpdata()
    {
        while(true)
        {
            DoUpdata();
            yield return new WaitForSeconds(spaceTime);
        }
    }

3.3 使用InvokeRepeating循环调用DoUpdata
    float repeatTime = 0.1f;
    public void Start()
    {
        InvokeRepeating("DoUpdata", 0, repeatTime);
        //StartCoroutine(StartUpdata());
    }

4. 使用对象池
操作目标相对较少,可简化对象池,实现效果
    GameObject prefab;
    Transform parent;

4.1 简化一
List<GameObject> objList = new List<GameObject>();
    GameObject CreatObj01()
    {
        //先遍历已有
        for (int i = 0; i < objList.Count; i++)
        {
            if (!objList[i].activeSelf)
            {
                objList[i].SetActive(true);
                /*
                 *  在此进行初始化
                 */
                return objList[i];
            }
        }
        //创建
        GameObject newObj = Instantiate(prefab, parent);
        /*
         *  在此进行初始化
         */
        objList.Add(newObj);
        return newObj;
    }
    void DestroyObj01(GameObject obj)
    {
        obj.SetActive(false);
        /*
         *  在此进行重置
         */
    }

4.2 简化二
 List<GameObject> workList = new List<GameObject>();
    List<GameObject> idleList = new List<GameObject>();
    GameObject CreatObj02()
    {
        GameObject newObj;
        //是否有空闲
        if (idleList.Count > 0)
        {
            newObj = idleList[0];
            idleList.RemoveAt(0);
            workList.Add(newObj);
        }
        else
        {
            //创建
            newObj = Instantiate(prefab, parent);
            workList.Add(newObj);
        }
        /*
         *  newObj 在此进行初始化
         */
        return newObj;
    }
    void DestroyObj02(GameObject obj)
    {
        if (workList.Contains(obj))
            workList.Remove(obj);
        idleList.Add(obj);
        obj.SetActive(false);
        /*
         *  在此进行重置
         */
    }
如果目标对象较多,就需要写一个正经的对象池了,因为之前写过,这里就直接给链接啦

5. 音效播放时,为避免频繁创建、销毁播放器,可以对音效统一管理,同样之前写过,上链接~

6. 场景中经常需要动态生成GameObject,当一次创建的数量较多时,在不影响使用的情况下,可以使用协程,在多帧内完成创建,可参照:

7. 部分简单的物理计算可以不使用Unity提供的物理系统,简化物理计算量,下面是关于向量计算的一个栗子:

8. 选择合理的数据结构存储数据。
在数据结构转换时,可以避免如下的for循环,利用C#提供的方法
        int[] array = new int[] { 1, 2, 3, 4, 5 };
        List<int> list = new List<int>();
        for (int i = 0; i < array.Length; i++)
        {
            list.Add(array[i]);
        }

9. 其他
9.1 尽量避免在Update和for循环内创建临时变量。
9.2 尽量避免创建临时字符串。
9.3 可以使用for循环的情况,就不用foreach。
9.4 每个继承MonoBehaviour的类,都会自动生成Update方法,但很多类是用不到Update的,这时候需要将其删除,毕竟实时调用空方法,多少还是有消耗的。
9.5 数值计算中使用乘法而不用触发,比如 a / 2, 可以写成 a * 0.5f。
9.6 比较他Tag值时,使用if(gameObject.CompareTag("Tag")),而不是if(gameObject.tag == "Tag")。
9.7 开发过程中会各种Debug,这也会有一定的消耗,可以对Debug进行封装,设置一个bool值,不过这样console面板双击不会跳转到调用Debug的代码行,最好的做法是将其做成dll文件。

OK~ 关于Unity性能优化中的代码优化技巧暂时就想到了这么多,期待大大们补充~~