Unity性能优化总结

开发Unity也有一段时间了,也学习了几招内存和性能优化的手段,虽然不系统,但是为了防止忘记,也是简单总结下。

资源缓存

在有些情况下,比如换装场景,一般的做法是我们加载服装的模型后,从中抽取SkinnedMeshRender,将多个SMR合并之后再通过骨骼绑定到人物身上,最后再把服装销毁。

如果我们只有一个人物没有问题,但是如果我们有n个裸体的人物形象,即使穿着同一套服装,也需要把所有的服装初始化n次,再销毁n次,这样对于CPU和内存都没有好处。

这时候,我们就可以采用缓存的方式,既然每次我们只需要抽取模型其中的SMR,那我们所有人物都从同一个模型中抽取就好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System.Threading.Tasks;
using UnityEngine;

namespace ResourceManager
{
public interface IResourceCache
{
public Task<GameObject> GetResource(string location);
public void ReleaseResource(string location);

public void Clear();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Character;
using UnityEngine;

namespace ResourceManager
{
public abstract class AbstractResourceCache: IResourceCache
{
private Dictionary<string, GameObject> _resourcesCache = new Dictionary<string, GameObject>();
private static GameObject _parent;
private int _maxCount = 20;

private static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);

public abstract Task<GameObject> InstantiateAsync(string key);
public abstract void Release(GameObject gameObject);

private GameObject GetParent()
{
if (!_parent)
{
_parent = new GameObject("ResourceCache");
}

return _parent;
}
public async Task<GameObject> GetResource(string location)
{
await _semaphoreSlim.WaitAsync();
try
{
if (_resourcesCache.ContainsKey(location))
{
if (_resourcesCache[location])
{
return _resourcesCache[location];
}
else
{
_resourcesCache.Remove(location);
}
}

GameObject resource = await InstantiateAsync(location);
resource.transform.parent = GetParent().transform;
resource.SetActive(false);

if (_resourcesCache.Count >= _maxCount)
{
ReleaseFirst();
}

_resourcesCache.Add(location, resource);
return resource;
}
finally
{
_semaphoreSlim.Release();
}
}

private void ReleaseFirst()
{
if (_resourcesCache.Count > 0)
{
ReleaseResource(_resourcesCache.First().Key);
}
}

public void ReleaseResource(string location)
{
try
{
if (_resourcesCache.ContainsKey(location))
{
if (_resourcesCache[location])
{
Release(_resourcesCache[location]);
}
_resourcesCache.Remove(location);
}

}
catch (Exception exception)
{
Debug.Log(exception);
}
}

public async void Clear()
{
await _semaphoreSlim.WaitAsync();
try
{
foreach (var item in _resourcesCache)
{
ReleaseResource(item.Key);
}

_resourcesCache.Clear();
}
catch (Exception exception)
{
Debug.Log(exception);
}
finally
{
_semaphoreSlim.Release();
}
}
}
}

资源池

上面那种资源缓存的方式,适用于所有物体共用同一套资源的情况,但是有的情况下是,我们可能需要独享资源,比如n个游戏角色需要被n个不同的玩家控制,我们就是需要n个实例出来。

这种情况下,我们还能怎么优化呢?

我们假设这样一种情况,如果这个房间会不断有玩家进出,每次一个玩家进来,游戏进程都需要加载一个对象,玩家退出,都需要销毁该对象,如果频率过高,其实对CPU也是一种负担。

这个时候我们就可以使用资源池的方式,实例化之后,如果玩家退出游戏,我们不销毁该对象,而是返回给资源池,等另一个玩家连接之后,我们直接从池子中拿一个给该玩家控制就好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System.Threading.Tasks;
using UnityEngine;

namespace ResourceManager
{
public interface IResourcePool
{
public Task<GameObject> GetResource(string location);
public GameObject GetResourceWithoutCreate(string key);

public void ReturnResource(string location, GameObject gameObject);
public void Clear();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

namespace ResourceManager
{
public abstract class AbstractResourcePool: IResourcePool
{
private static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
private Dictionary<string, List<GameObject>> _pool =
new Dictionary<string, List<GameObject>>();

private GameObject _parent;

private int _maxCount = 16;

public abstract Task<GameObject> InstantiateAsync(string key);
public abstract void Release(GameObject gameObject);

private GameObject GetParent()
{
if (!_parent)
{
_parent = new GameObject("ResourcePool");
}

return _parent;
}

public GameObject GetResourceWithoutCreate(string key)
{
if (_pool.ContainsKey(key))
{
while (_pool[key].Count > 0)
{
GameObject resource = _pool[key].First();
if (resource != null)
{
resource.SetActive(true);
_pool[key].Remove(resource);
return resource;
}
else
{
_pool[key].RemoveAt(0);
}
}
}

return null;
}

public async Task<GameObject> GetResource(string key)
{
await _semaphoreSlim.WaitAsync();
try
{
if (_pool.ContainsKey(key))
{
while (_pool[key].Count > 0)
{
GameObject resource = _pool[key].First();
if (resource != null)
{
resource.SetActive(true);
_pool[key].Remove(resource);
return resource;
}
else
{
_pool[key].RemoveAt(0);
}
}
}

GameObject newResource = await InstantiateAsync(key);
newResource.transform.SetParent(GetParent().transform);
return newResource;
}
finally
{
_semaphoreSlim.Release();
}
}

public async void ReturnResource(string key, GameObject gameObject)
{
await _semaphoreSlim.WaitAsync();
try
{
gameObject.SetActive(false);
gameObject.transform.SetParent(GetParent().transform);
if (_pool.ContainsKey(key))
{
if (_pool[key].Count >= _maxCount)
{
Release(gameObject);
}
else
{
_pool[key].Add(gameObject);
}
}
else
{
_pool.Add(key, new List<GameObject>(){gameObject});
}
}
finally
{
_semaphoreSlim.Release();
}
}

public void Clear()
{
foreach (var item in _pool)
{
foreach (var obj in item.Value)
{
Release(obj);
}
}
}
}
}

Material尽量复用同一个

如果条件允许,比如所有的物体的material是同一个,而且可以同时变化,就不要为每一个物体新建一个material。

合并Texture

合并Texture之后,如果后面不需要修改,尽量对新的Texture调用Apply方法,释放用于修改的内存。

合并批次

https://zhuanlan.zhihu.com/p/196691845

如果贴图不需要自适应,关掉minmap