赞
踩
总结自麦子学院Unity性能优化教程。文末附幕布思维导图总结。
我们玩过的游戏中,神优化和渣优化的案例都不胜枚举,它们直接影响着玩家们的游戏体验。这篇文章将从最基础的概念开始,配合实例讲解各种优化的技巧,最终建立起一套属于自己的优化方法,完成对游戏项目的评估和优化工作。
1.性能优化 运行效率空间占用
2.流程优化 精简某个功能的流程
3.体验优化 音乐的节奏,背景的渲染,剧情的跌宕起伏
1.游戏流畅运行
1.多种帧数标准 30帧手游 端游60帧
2.避免卡顿 加载大场景,玩家数量增多,怪物增多
2.游戏符合市场需要
1.硬件兼容性 取舍市场占有率小的机型
2.安装包/数据包大小 平台渠道的安装包大小限制(60M)
2.用户不察觉
玩家不一定能发现欠优化的地方 玩家感觉不出来的地方
玩家不一定能发现优化欠佳的地方 优化后玩家也感觉不到差别的地方
这里要多查看 官网的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));
}
}
}
常见的性能黑点(正确的代码放到了错误的地方)底层!!!
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 () {
}
}
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。这就是效率。
如何找到需要优化的代码
在 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);
}
}
}
美术资源
对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在像素着色器。
使用每个片段较少纹理。
设置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();
}
}
缩减包体积
设置 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
如何找到打包出的资源占比
先进行打包,打包完毕后查看控制台输出
打开文本日志,查看各资源的占比,确定优化的方向(此处是空场景测试)
建立一套属于自己的优化方法
1.确定优化目标 (帧数)(卡顿)
2.选择合适的工具(Profiler)(SRDebugger)
3.找到性能瓶颈(脚本)(图形)(绕开)
4.无法解决(找手册)(问google)(绕开)
5.经常与团队沟通
另存为保存到本地可以看清楚图片。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。