项目遇到了卡顿的情况 仔细检查了代码没检查出有误的地方
仔细的总结了一下可以优化的东西
解决了卡顿 记录一下
项目之前写的关于倒计时之类的东西 都是开了个协程
虽然协程是消耗很小的线程 , 可是还是有额外消耗
而且 有很多用携程来检测销毁预制体的操作 也都放到Update方法里面解决了
解决方案:放到Update方法里面去执行,协程能不用就不用
还有: 协程的yield 一般都会用到这个 yield return new WaitForSeConds(1f);
如果很多协程 都用 new WaitForSeConds(1f);
那就直接把他变成一个变量 大家公用就行了 这样也不用每次都new一个了
反正只要有new 就会有额外的占用 这是一个容易忽略的地方
百度查到的分析 :
在c#中,字符串是引⽤类型变量⽽不是值类型变量,即使看起来它是存储字符串的值的。这就意味着字符串会造成⼀定的内存垃圾,
由于代码中经常使⽤字符串,所以我们需要对其格外⼩⼼。
c#中的字符串是不可变更的,也就是说其内部的值在创建后是不可被变更的。每次在对字符串进⾏操作的时候(例如运⽤字符串
的“加”操作),unity会新建⼀个字符串⽤来存储新的字符串,使得旧的字符串被废弃,这样就会造成内存垃圾。
我们可以采⽤以下的⼀些⽅法来最⼩化字符串的影响:
减少不必要的字符串的创建,如果⼀个字符串被多次利⽤,我们可以创建并缓存该字符串。
减少不必要的字符串操作,例如如果在Text组件中,有⼀部分字符串需要经常改变,但是其他部分不会,则我们可以将其分为两个部分的组件,对于不变的部分就设置为类似常量字符串即可,比如下⾯的例⼦。
如果我们需要实时的创建字符串,我们可以采⽤StringBuilderClass来代替,StringBuilder专为不需要进⾏内存分配⽽设计,从⽽减少字符串产⽣的内存垃圾。 移除游戏中的Debug.Log()函数的代码,尽管该函数可能输出为空,对该函数的调⽤依然会执⾏,该函数会创建⾄少⼀个字符(空字符)的字符串。如果游戏中有⼤量的该函数的调⽤,这会造成内存垃圾的增加。
在下⾯的代码中,在Update函数中会进⾏⼀个string的操作,这样的操作就会造成不必要的内存垃圾。
正常写一般这样 (内存方面来说 是有问题的)
public Text tx_time;
float timer;
void Update() {
timer += Time.deltaTime;
tx_time.text = "Time:" + timer.ToString();
}
解决方案: 把一个Text分成两个Text来拼接
这样写的话 内存没有额外增加 可是代码看起来好蠢 ! 蠢 ! 蠢 !
虽然知道了这样写内存最优 可是我们还是没有采用 先记录一下吧
public Text tx_str;
public Text tx_time;
float timer;
void Start() {
tx_str.text = "Time:";
}
void Update() {
tx_time.text = timer.ToString()
}
比如碰撞方法 有时需要Tag来判断进行一些操作
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "tag")
{
//操作
}
}
直接使用gameObject.tag会产生内存垃圾
解决方案:
使用gameObject.CompareTag()
if (other.gameObject.CompareTag(“tag”))
{
}
创建
GameObject obj = Instantiate(prefab);
销毁
Destroy( gameObject ) ;
这样是没问题 可是如果很多预制体来频繁创建和消耗 那就是有问题了
于是做了线程池进行优化 , 该销毁时 用隐藏来替代销毁的操作
public class ObjectPool
{
private static ObjectPool instance;
private Dictionary<string, Queue<GameObject>> objectPool = new Dictionary<string, Queue<GameObject>>();
private GameObject pool;
public static ObjectPool Instance
{
get
{
if (instance == null)
{
instance = new ObjectPool();
}
return instance;
}
}
public GameObject GetObject(GameObject prefab)
{
GameObject _object;
if (pool == null) //当场景没有对象池时(第一次进入游戏或者切换了场景),新建一个对象池游戏物品并清空字典
{
pool = new GameObject("ObjectPool");
objectPool = new Dictionary<string, Queue<GameObject>>();
}
if (!objectPool.ContainsKey(prefab.name) || objectPool[prefab.name].Count == 0)
{
_object = GameObject.Instantiate(prefab);
PushObject(_object);
GameObject childPool = GameObject.Find(prefab.name + "Pool");
if (!childPool)
{
childPool = new GameObject(prefab.name + "Pool");
childPool.transform.SetParent(pool.transform);
}
_object.transform.SetParent(childPool.transform);
}
_object = objectPool[prefab.name].Dequeue();
_object.SetActive(true);
return _object;
}
public void PushObject(GameObject prefab)
{
string _name = prefab.name.Replace("(Clone)", string.Empty);
prefab.name = _name ;
if (!objectPool.ContainsKey(_name))
objectPool.Add(_name, new Queue<GameObject>());
objectPool[_name].Enqueue(prefab);
prefab.SetActive(false);
}
}
这样 做到了预制体复用 不用频繁创建销毁 这个优化对项目影响挺大
主动调⽤GC操作 在不影响游戏体验的时候 (场景切换等),我们可以主动的调⽤GC操作:
System.GC.Collect()
以上 是总结的一些优化的地方 , 还请大佬指点一下 谢谢
2023年03月08日19:06:04