游戏测试中常见细节优化实践

游戏开发中一些细节优化

1.能整不浮,能乘不除

看一下代码

		float f_a = 66666888f;
        float f_b = f_a + 0.01f;
        Debug.Log(f_a);//1.677722E+07
        Debug.Log(f_b);//1.677722E+07
        Debug.Log("_______________****_______________");
        double d_a = 9007199254740992f;
        double d_b = d_a + 0.01f;
        Debug.Log(d_a);//9.00719925474099E+15
        Debug.Log(d_b);//9.00719925474099E+15

输出结果;
游戏测试中常见细节优化实践_第1张图片
实际上
一是二者存储结构不同;
二就是就是浮点数精度的问题;

float把 32位分成了3部分,1位(符号位)8位(指数位)23位(有效数字)那么 1+ 8 + 23 等于32吧,所以float的32位是这么来的。23位有效数字就表示float真正能存的精度,23位小数部分是反被储存的部分,所以它是有24位存储的,2^24(2的24次方)=16777216 。
如果程序中有一个float的数值运算后的小数部分,如果超过16777216.xxx后运算的结果就会不准确;
double则是1位符号位+11位指数+52位有效数字 = 64位
有效数2^53(2的53次方)=9007199254740992,超过9007199254740992.xxx后运算的结果就会不准确;

浮点数计算结果不同的CPU计算出来可能是不一致的,像帧同步等就基本告别浮点数了,尽量用整型代替浮点型,实在需要可以用定点数,但也要注意是否存在定点数转回浮点数的现象;
其实还可以巧用位操作符,
位操作符比传统乘除效率要高,适合大量计算时使用
例如:
int a = 100 >> 1 相当于除2取整 结果为 50
int a = 100 << 2 相当于乘4取整 结果为 400
不过不必要过分追求位运算,在许多比较老的微处理器上, 位运算比加减运算略快, 通常位运算比乘除法运算要快很多,但现代架构中, 情况并非如此:位运算的运算速度通常与加法运算相同(仍然快于乘法运算)。在现代处理机架构中编译器一般会自动优化为移位运算的。还有很多芯片已经内置了硬件乘法器(乘法器的模型就是基于“移位和相加”的算法);

2.MonoBehaviour

1.MonoBehaviour中,如果没有相应的事件要处理,要删除默认的空函数;
Update、FixedUpdate、LateUpdate中,如果没必要每帧的逻辑,可以降低频率

Void Update(){
    if(Time.frameCount%6==0{
        //要执行的功能函数
    }
}

2.如果间隔更长,没必要每帧的逻辑,使用周期性的协程更妥当,例如使用InvokeRepeating函数:

InvokeRepeating();

3.Gameobject不可见时,设置 enabled = false 时,update 就会停止调用。
4. FindGameObjectWithTag或者GetComponent<>等查找操作放在Star()先引用,不要放在Update或者FixedUpdate里;
5. 协程使用 yield return new WaitForSeconds() 将会每帧导致 大概21Byte GC,而yield return null 会产生 大概9 Byte GC;
我们可以简单地通过复用一个全局的 WaitForEndOfFrame 对象来优化掉这个开销:

static WaitForEndOfFrame EndOfTest = new WaitForEndOfFrame();
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(Test());
    }
    IEnumerator Test()
    {
        yield return null;

        while (true)
        {
           //原本是yield return new WaitForEndOfFrame();
            yield return EndOfTest;
            
        }
    }

实际上实际上,所有继承自YieldInstruction 的用于挂起协程的指令类型,都可以使用全局缓存来避免不必要的 GC 负担。常见的有:
WaitForSeconds
WaitForFixedUpdate
WaitForEndOfFrame
可以自己新建文件xxx.cs这个文件里,集中地创建了上面这些类型的静态对象,使用时可以直接这样:
yield return xxx.GetWaitForSeconds(1.0f);
6.使用内置数据代替新建数据
例如: 使用 Vector3.up 而不是 new Vector(0, 0, 0);
游戏测试中常见细节优化实践_第2张图片
7.缓存组件,缓存Gameobject,调用 GetComponent 函数会有查找开销,用变量挂载到脚本使用,降低开销(GetComponent时如果获取到空的组件也会产生GC)。在脚本挂载对象引用,可减少查找。
8.查找对象标签用if (go.CompareTag (“xxx”)来代替if (go.tag == “xxx”)。GameObject.tag会在内部循环调用对象分配的标签属性,并分配额外的内存,并且效率也更低。
9.使用委托机制代替SendMessage,BroadcastMessage,SendMessageUpwards这三个函数。
因为它们的实现是一种伪监听者模式,利用的是反射机制,性能非常低。具体的性能大概是委托的十分之一。

3.Instantiate实例化操作技巧

需要频繁实例化的gameobject比如子弹,需要放入对象池,可以大量减少实例化,销毁的开销。将部分耗时资源进行预载。

4.复杂的UI界面和带有动画组件的GameObject不要频繁切换Active/Deactive

设置Active/Deactive复杂对象所用的耗时会比较大,这里可以使用一个小技巧,可以将需要Deactive的操作变成将GameObject移动到比较远的,摄像机之外。然后将GameObject上面的全部Component的enabled属性设置成false。Active时再重新设置回来

5.使用for或者while代替foreach

foreach每次调用会产生有40Byte左右的GC数据,产生 GC 的根本原因是使用了 using 语句。(GetEnumerator()返回值类型,在
using 中装箱了)

6.合理使用数组,ArrayList,List

数组:内存中是连续存储的,索引速度非常快,赋值与修改元素也很简单。但不利于动态扩展以及移动。因为数组的缺点,就产生了 ArrayList。
ArrayList:使用该类时必须进行引用,同时继承了 IList 接口,提供了数据存储和检索,ArrayList对象的大小动态伸缩,支持不同类型的结点。
ArrayList虽然很完美,但结点类型是 Object,故不是类型安全的,也可能发生装箱和拆箱操作,带来很大的性能耗损。
List是泛型接口,规避了 ArrayList的两个问题。

你可能感兴趣的:(性能,游戏,优化)