Unity性能优化

总结自麦子学院Unity性能优化教程。文末附幕布思维导图总结。

我们玩过的游戏中,神优化和渣优化的案例都不胜枚举,它们直接影响着玩家们的游戏体验。这篇文章将从最基础的概念开始,配合实例讲解各种优化的技巧,最终建立起一套属于自己的优化方法,完成对游戏项目的评估和优化工作。

1. 简介

1.什么是性能优化

1.常见的优化类型

  • 1.性能优化 运行效率空间占用

  • 2.流程优化 精简某个功能的流程

  • 3.体验优化 音乐的节奏,背景的渲染,剧情的跌宕起伏

2.性能优化目标

  • 1.游戏流畅运行

    1.多种帧数标准 30帧手游 端游60帧
    2.避免卡顿 加载大场景,玩家数量增多,怪物增多

  • 2.游戏符合市场需要
    1.硬件兼容性 取舍市场占有率小的机型
    2.安装包/数据包大小 平台渠道的安装包大小限制(60M)

3.优化常见的误区

  • 误区1:我的游戏很简单不需要优化 (性能黑点) 游戏简单是结构玩法简单,不代表不需要进行优化
  • 误区2:优化工作尽早进行 优化工作需要在后期进行,针对主要因素,避免为了5%的问题话费95%的时间
  • 误区3:性能优化=Debug 性能优化的前提是解决所有的代码错误

4.优化的两大原则

  • 1.不过早做优化
  • 2.用户不察觉

    玩家不一定能发现欠优化的地方 玩家感觉不出来的地方
    玩家不一定能发现优化欠佳的地方 优化后玩家也感觉不到差别的地方

5.所有游戏都需要优化吗?

  • 性能完美是我们追求的目标
  • 不同类型的游戏对优化的侧重点不一样
  • 优化工作会占生命周期非常大的一部分

2. Profiler

这里要多查看 官网的API 在Manual 里面搜索 Profiler
Unity3d-Window-Profiler 打开方式。
这个只能是性能评估工具,不是优化工具。
观察Profiler 窗口 测试代码:

using UnityEngine;
using System.Collections;

public class BadExampleProfiler : MonoBehaviour {

    public GameObject cube;

    // Update is called once per frame
    void Update () {

        Loop ();
    }

    void Loop()
    {
        for (int i = 0; i < 100; i++) {
            float x = Random.Range (0,360f);
            float y = Random.Range (0,360f);
            float z = Random.Range (0,360f);
            Vector3 randomVector = new Vector3 (x,y,z);
            Object.Instantiate (cube, randomVector, Quaternion.Euler(randomVector));
        }
    }
}

3.优化的组成部分

脚本


常见的性能黑点(正确的代码放到了错误的地方)底层!!!

1.常规循环   尽量少放在Update

2.变量的隐性调用  go.transform不推荐 (这是一种遍历查找组件的方式) 推荐GetComponent(Transform)(Markdown不支持应该是尖角括号)

3.Gmaeobject.Find 推荐使用Tag查找 保存变量 

4.多线程
协程处理,大量循环使用分携程执行,前边添加yield return设置执行顺序
    IEnumerator Work()
    {
        //线程不安全
        //StartCoroutine(MyIoWork1());
        //StartCoroutine(MyIoWork2());
        yield return StartCoroutine(MyIoWork1());
        yield return StartCoroutine(MyIoWork2());
    }

    IEnumerator MyIoWork1()
    {
        for (int i = 0; i < 1000; i++)
        {
            System.IO.File.Delete("c:\a.zip");
            yield return null;
        }

    }

    IEnumerator MyIoWork2()
    {
        for (int i = 0; i < 1000; i++)
        {
            System.IO.File.Delete("c:\a.zip");
            yield return null;
        }

    }

数学 合理降低计算的精度

这里写图片描述
这里写图片描述
计算距离:Mathf.Sqrt
计算方向:Vector3.Angle
Minimize use of complex mathematical operations such as pow, sin and cos in pixel shaders.

using UnityEngine;
using System.Collections;

public class TestMagnitude : MonoBehaviour {

    public Transform player1;
    public Transform player2;

    void Start()
    {
        Calc();
    }

    void Calc()
    {
        float distance1 = (player1.position - transform.position).magnitude;
        float distance2 = (player2.position - transform.position).magnitude;
        Debug.Log("Player1 is closer than Player2:" + (distance1 < distance2).ToString());

        float distance11 = (player1.position - transform.position).sqrMagnitude;
        float distance22 = (player2.position - transform.position).sqrMagnitude;
        Debug.Log("Player1 is closer than Player2:" + (distance1 < distance2).ToString());
    }
}
using UnityEngine;
using System.Collections;

public class Compare : MonoBehaviour {

    public Transform player1;
    public Transform player2;

    // Use this for initialization
    void Start () {

        float angle = Vector3.Angle(player2.forward, player1.forward);
        float dot = Vector3.Dot(player2.forward,player1.forward);

        Debug.Log("Dot=" + dot +" Angle=" + angle);
    }

    // Update is called once per frame
    void Update () {

    }
}

Object Pool 适用于频繁操作的对象

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

public class TestPool : MonoBehaviour
{
    public Transform root;
    public GameObject prefab;

    public float loopCount;
    public float prefabCount;
    List<GameObject> objects;

    // Use this for initialization
    void Start()
    {

        objects = new List<GameObject>();
        System.DateTime startTime = System.DateTime.Now;
        TestCaseWaitOutPool();
        System.DateTime endTime = System.DateTime.Now;
        string totalMs = (endTime - startTime).TotalMilliseconds.ToString();

        Debug.Log("Test case Without Pool take " + totalMs + "ms.");
    }

    // Update is called once per frame
    void Update()
    {

    }

    void TestCaseWaitOutPool()
    {
        for (int j = 0; j < loopCount; j++)
        {
            for (int i = 0; i < prefabCount; i++)
            {
                //create prefab
                GameObject go = Instantiate(prefab);
                //set parent
                go.transform.parent = root;
                //add to list
                objects.Add(go);
            }
            for (int i = 0; i < prefabCount; i++)
            {
                GameObject.Destroy(objects[i]);
            }

            //destory prefab
            objects.Clear();
        }

    }
}

使用 Pool 之后。

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

public class TestPool : MonoBehaviour
{
    public SimplePool pool;
    public Transform root;
    public GameObject prefab;

    public float loopCount;
    public float prefabCount;
    List<GameObject> objects;

    // Use this for initialization
    void Start()
    {

        objects = new List<GameObject>();
        // with out pool
        System.DateTime startTime = System.DateTime.Now;
        TestCaseWaitOutPool();
        System.DateTime endTime = System.DateTime.Now;
        string totalMs = (endTime - startTime).TotalMilliseconds.ToString();

        Debug.Log("Test case Without Pool take " + totalMs + "ms.");

        // with pool
        System.DateTime poolstartTime = System.DateTime.Now;
        TestCaseWithPool();
        System.DateTime poolendTime = System.DateTime.Now;
        string pooltotalMs = (poolendTime - poolstartTime).TotalMilliseconds.ToString();

        Debug.Log("Test case With Pool take " + pooltotalMs + "ms.");
    }

    void TestCaseWaitOutPool()
    {
        for (int j = 0; j < loopCount; j++)
        {
            for (int i = 0; i < prefabCount; i++)
            {
                //create prefab
                GameObject go = Instantiate(prefab);
                //set parent
                go.transform.parent = root;
                //add to list
                objects.Add(go);
            }
            for (int i = 0; i < prefabCount; i++)
            {
                GameObject.Destroy(objects[i]);
            }

            //destory prefab
            objects.Clear();
        }

    }

    void TestCaseWithPool()
    {
        for (int i = 0; i < loopCount; i++)
        {
            List<GameObject> objectsList = pool.GetObjects((int)prefabCount);
            pool.DestroyObjects(objectsList);
        }

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

public class SimplePool : MonoBehaviour
{
    public Transform root;
    public GameObject prefab;
    public int size;

    List<GameObject> pooled;

    void Start()
    {
        pooled = new List<GameObject>();
        Prewarm();
    }
    void Prewarm()
    {
        PoolObjects(size);
    }

    List<GameObject> PoolObjects(int _amount)
    {
        List<GameObject> newPooled = new List<GameObject>();
        for (int i = 0; i < _amount; i++)
        {
            GameObject go = Instantiate(prefab);
            go.transform.parent = root;
            go.SetActive(false);
            newPooled.Add(go);
        }
        pooled.AddRange(newPooled);
        return newPooled;
    }

    public List<GameObject> GetObjects(int _amount)
    {
        List<GameObject> pooledObjects = pooled.FindAll(_go => !_go.activeSelf);
        if (pooledObjects.Count < _amount)
        {
            List<GameObject> newObjects = PoolObjects(_amount - pooledObjects.Count);
            pooledObjects.AddRange(newObjects);
            foreach (var go in pooledObjects)
            {
                go.SetActive(true);
            }
            return pooledObjects;
        }
        else
        {
            foreach (var go in pooledObjects)
            {
                go.SetActive(true);
            }
            return pooledObjects;
        }
    }

    public void DestroyObjects(List<GameObject> objects)
    {
        for (int i = 0; i < objects.Count; i++)
        {
            objects[i].SetActive(false);
        }
    }
}

Test case Without Pool take 226.1487ms.
UnityEngine.Debug:Log(Object)
TestPool:Start() (at Assets/TestPool.cs:26)
Test case With Pool take 58.0407ms.
UnityEngine.Debug:Log(Object)
TestPool:Start() (at Assets/TestPool.cs:34)

时间从 226.1487ms 缩短到58.0407ms。这就是效率。


如何找到需要优化的代码

Total 与 Self

在 Unity-Window-Profiler
Overview 里面的 Total(总的占用包括调用其他人的部分),Self(仅自身占用)

using UnityEngiusing System.Collections;

public class TestTime : MonoBehaviour {

    public GameObject prefab;

    void Start()
    {
        //self
        System.Threading.Thread.Sleep(2000);

        Create(); // others
    }

    void Create()
    {
        for (int i = 0; i < 10000; i++)
        {
            GameObject go = GameObject.Instantiate(prefab);
            GameObject.Destroy(go);
        }
    }
}

4. 图形优化

美术资源
对Mesh的优化( Mesh Baker)
打开Game场景Stats 观察 Tris(三角形),Verts(顶点数)
模型面数
在这里可以使用插件 Simple LOD可以生成更低的模型tris减少,但是模型会变得不清晰

using UnityEngine;
using System.Collections;

public class TestLod : MonoBehaviour {

    public float[] camDistance;
    public Mesh[] lod;
    SkinnedMeshRenderer skin;

    // Use this for initialization
    void Start () {

        skin = GetComponent<SkinnedMeshRenderer>();
    }

    // Update is called once per frame
    void Update () {

        int level = GetLevel();
        skin.sharedMesh = lod[level];
    }

    int GetLevel()
    {
        float distance = Vector3.Distance(transform.position,Camera.main.transform.position);
        for (int i = 0; i < camDistance.Length; i++)
        {
            if (distance < camDistance[i])
            {
                return i;
            }
        }
        return camDistance.Length;
    }
}

Material
SetPass Calls 在移动平台上最好不要超过60或者80,PC 300
2D UI 打包成图集 减少setpass calls
3D 合并材质
本质 共享材质,压缩材质

Shader
Main Maps 越少越好
这里在推荐一个插件: shader Fore
使用 Mobile 使用 图形化工具

粒子
数量 数量要限制
透明
材质 /shader 移动端:SM2.0.

物理效果
1.镜头
Clipping Planes
Occlusion Culling 默认勾上了。但是没有任何效果
打开Window-Occlusion Culling 需要bake 一下
需要bake的东西,必须是Static
Smallers Occluder 挡住后面的东西,优化做不好,就可能是负优化
在Scene场景,选中摄像机,可以设置Occlusion Culling是Edit或者Visualize。这个时候随着镜头的移动,镜头中的物体就会动态的显示了。
从屏幕上看到的点,都不会剔除掉的。
Unity 3专业版内置了一个强大的 Occlusion Culling 插件 Umbra免费的

2.光照
Bake and Probes
我们的目标就是降低:SetPass Calls
Light 选则Bake光,使用静态光。出现色差
设置:Scenes In Build Player Setting
找到Color Space 有 Linear(不支持移动端) Gamma
缺点:移动的物体不会受到光照的影响。这个时候就需要创建光照探针了。Light-Light Probe Group.记录静态光照的效果。

3.碰撞
Collider尽可能简单
控制rigidbody数量
Rigidbody检查方式,检测间隔,Collision Detection 持续的。离散型的。

4.CheckList
Simple checklist to make your game faster
对于PC建筑(取决于目标GPU)时,请记住下面的200K和3M顶点数每帧。
如果你使用内置着色器,从挑选的那些移动或熄灭类别。他们在非移动平台以及工作,但更复杂的着色器的简化和近似版本。
保持每个场景低的不同材料的数量,并共享不同的对象尽可能之间尽可能多的材料。
将Static非运动物体的属性,以允许像内部优化静态批次。
只有一个(最好是定向)pixel light影响几何体,而不是整数倍。
烘烤照明,而不是使用动态照明。
尽可能使用压缩纹理格式,以及超过32位纹理使用16位纹理。
避免使用雾在可能的情况。
使用遮挡剔除,以减少可见的几何图形的量和抽取呼叫中的有很多闭塞复杂静态场景的情况。闭塞记扑杀设计你的水平。
使用包厢到“假”遥远的几何体。
使用像素着色器或纹理组合搭配,而不是多遍方法有几个纹理。
使用half精度变量在可能的情况。
尽量减少使用复杂的数学运算,如的pow,sin并cos在像素着色器。
使用每个片段较少纹理。

5. 文件优化

1.AssetBundle 创建 读取

设置Prefab。
AssetBundle New:env/go 路径自行设置


using UnityEngine;
using System.Collections;
using System;

public class TestAssetBundle : MonoBehaviour {

    public string path;
    public string file;

    // Use this for initialization
    void Start () {

        StartCoroutine( Load());
    }

    IEnumerator Load()
    {
        string _path = "file:///" + Application.dataPath + path;
        WWW www = WWW.LoadFromCacheOrDownload(_path,1);
        yield return www;

        AssetBundle bundle = www.assetBundle;
        AssetBundleRequest request = bundle.LoadAssetAsync(file);
        yield return request;

        GameObject prefab = request.asset as GameObject;
        Instantiate(prefab);

        //Clean
        bundle.Unload(false);
        www.Dispose();
    }

}

2.移动端打包优化

缩减包体积
设置 Andriod player setting Optimlzation .NET2.0(完整版) .NET2.0 Subset(简化版)
Stripping Leve Disabled .Strip Assembies. Strip Byte Code(一般用这个就可以了) .Use Micro mscorlib

1.momo version 
    full 
    subset 
2.stripping level 
    disabled 
    strip bute code 
3.媒体文件 
    图片 psd/png/jpg 
    音频 ogg/mp3/wav 
    fbx 公用animationclip

如何找到打包出的资源占比
先进行打包,打包完毕后查看控制台输出
这里写图片描述
打开文本日志,查看各资源的占比,确定优化的方向(此处是空场景测试)
这里写图片描述

3.跨平台开发效率优化

  • 节省时间 utomate 自动化打包插件
  • 显示面板改进 Odin - Inspector and Serializer
  • DebugConsole Editor Console Pro

6. 总结

建立一套属于自己的优化方法
1.确定优化目标 (帧数)(卡顿)
2.选择合适的工具(Profiler)(SRDebugger)
3.找到性能瓶颈(脚本)(图形)(绕开)
4.无法解决(找手册)(问google)(绕开)
5.经常与团队沟通

这里写图片描述
另存为保存到本地可以看清楚图片。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页