当前位置: 首页 > article >正文

Unity高效编程经验50条分享

1.避免频繁创建临时对象

  • 错误写法:obj.transform.position = pos;这种写法会在Lua中频繁返回transform对象导致gc
  • 正确写法:创建一个静态方法来设置位置,例如
    class LuaUtil {
    static void SetPos(GameObject obj, float x, float y, float z) {
    obj.transform.position = new Vector3(x, y, z);
    }
    }
    Lua中调用LuaUtil.SetPos(obj, float x, float y, float z);

2.使用泛型必须生成对应类型的wrap文件

  • 错误写法:泛型容器直接使用
  • 正确写法:_GT(typeof(Dictionary<int, long>)然后生成对应的wrap文件,Lua调用避免产生GC

3.使用C#容器如果知道大概的容量尽量指定初始化大小避免自动扩容产生的GC,例如List、Dictionary、Array

  • 错误写法:
    Dictionary<ulong, int> buffTableIndexMap = new Dictionary<ulong, int>();
    List list = new List();
    for (int i = 0; i < 1000; i++)
    {
    list.Add(i);
    }
  • 正确写法:Dictionary<ulong, int> buffTableIndexMap = new Dictionary<ulong, int>(1000);
    List list = new List(1001);
    for (int i = 0; i < 1000; i++)
    {
    list.Add(i);
    }

4.注意非托管类型的手动释放

  • 错误写法:
    • Marshal.AllocHGlobal没有调用Marshal.FreeHGlobal
    • 继承自IDisable接口的对象没有手动调用dispose方法
  • 正确写法:
    • 调用手动释放内存的接口方法

5.注意循环内部

  • Unity携程
    • 错误写法:大量的使用Coroutine
    • 正确写法:使用Update自己管理状态
  • 携程等待
    • 错误写法:大量使用yield return new WaitForEndOfFrame();或者new WaitForSeconds(3f)或者new WaitForFixedUpdate()等
    • 正确写法:保存一个new等待对象,重复使用
  • New 对象
    • 错误写法:循环内部不断new 对象
    • 正确写法:使用对象池循环使用
  • 频繁显/隐 SetActive
    • 错误写法:频繁调用
    • 正确写法:可以移到相机视野外或者设置透明度
  • 频繁操作组件方法/属性
    • GetComponent
    • AddComponent
    • Find/FindWithTag
    • GetTag
    • Camera.main 内部使用的FindGameObjectsWithTag()
    • Object.name
    • 错误写法:频繁调用
      function Update()
      local cube = CS.UnityEngine.GameObject.Find(“Cube”)
      cube.transform:Rotate(CS.UnityEngine.Vector3(0, 90, 0) * CS.UnityEngine.Time.deltaTime)
      end
      void Update()
      {
      Camera.main.orthographicSize //Some operation with camera
      }
    • 正确写法:第一次获取之后缓存之后复用
      – 正确写法:在Start中获取组件并缓存
      function Start()
      self.cube = UnityEngine.GameObject.Find(“Cube”).transform
      end

– 在Update中使用缓存的组件
function Update()
self.cube:Rotate(UnityEngine.Vector3(0, 90, 0) * UnityEngine.Time.deltaTime)
end
private Camera cam;
void Start()
{
cam = Camera.main;
}
void Update()
{
cam.orthographicSize;
}

  • SendMessage
    • 错误写法:直接使用SendMessage方法,内部调用了反射
    • 正确写法:使用事件系统

6.字符串处理

  • 大量字符串处理
    • 错误写法:大量字符串拼接、切割
      void Update()
      {
      text.text = "Player " + name + " has score " + score.toString();
      }
    • 正确写法:尽量减少这种使用场景,用stringbuilder或者用ZString替代(原理:基于Span动态数组实现的0gc字符串方案)
      void Start()
      {
      text = GetComponent ();
      builder = new StringBuilder(50);
      }
      void Update()
      {
      builder.Length = 0;
      builder.Append("Player “);
      builder.Append(name);
      builder.Append(” has score ");
      builder.Append(score);
      text.text = builder.ToString();
      }
  • 大量使用正则表达式
  • 使用linq查询
    • 错误写法:
      var query = from item in myList
      where item.SomeProperty == someValue
      select item
      linq会分配临时空间
    • 正确写法:使用for循环遍历

7.C#、Lua数据传递交互

  • 错误写法:把字符串当key传入,例如:
    luaState.Push(MBuffMgr.UID_str);
    luaState.Push(buff.UID);
    或者
    theSubTable[MBuffMgr.UID_str] = buff.UID;
  • 正确写法:使用数组而不是哈希表,例如
    theSubTable[1] = buff.UID
    或者使用枚举
    theSubTable[(int)EBuffAttr.UID] = buff.UID

8.物理接口的使用
使用物理接口例如:Physics.Raycast、Physics.SphereCast、Physics.BoxCast和Physics.CapsuleCast等方法

  • 错误写法:不注意检测长度和检测layer
  • 正确写法:正确评估检测长度和设置必要的检测layer,减少不必要的物理计算量

9.同步异步接口使用
例如资源加载接口

  • 不推荐写法:使用同步接口
  • 推荐写法: 使用异步接口

10.分帧加载

  • 错误写法:
    length比较大
    for (int i = 0; i < prefabs.Length; i++)
    {
    Instantiate(prefabs[i], parent);
    }
  • 正确写法:
    IEnumerator LoadPrefabs()
    {
    var waitForOneSecond = new WaitForSeconds(1);
    for (int i = 0; i < prefabs.Length; i++)
    {
    yield return waitForOneSecond; // 每帧延迟1秒,可以根据需要调整
    Instantiate(prefabs[i], parent);
    }
    }

11.注意Lua与C#交互传递Unity特有的值类型
Lua调用C#函数时,涉及到参数的传递和返回值的处理,这些操作都会产生性能开销。特别是当涉及到Unity特有的值类型(如Vector3、Quaternion等)时,性能消耗更大,因为需要进行类型转换

  • 错误写法:
    function Update()
    local cube = CS.UnityEngine.GameObject.Find(“Cube”)
    cube.transform.rotation = CS.UnityEngine.Quaternion.Euler(0, 45 * CS.UnityEngine.Time.deltaTime, 0)
    end

  • 正确写法
    function Start()
    self.cubeTransform = CS.UnityEngine.GameObject.Find(“Cube”).transform
    end

– 在Update中使用缓存的组件
function Update()
LuaUtil.SetRotation(self.cubeTransform, 0, 45 * UnityEngine.Time.deltaTime, 0)
end
C#的封装方法
public static SetRotation(transform, float x, float y, float z)

12.lua判空的写法
判空防止==误写成=

  • 不推荐写法:if theMap == nil then
  • 建议写法:if nil == theMap then

13.代码规范
建议按照约定熟成的方式,最大的收益方便搜索

  • 驼峰命名
  • 左右空格
  • 枚举类型使用E开头
    空格不要多也不要少
    错误写法:table[i +1] =1
    正确写法:table[i + 1] = 1

14.不要写立即数
影响代码阅读和统一修改

  • 错误写法:
    table[1] = 1 – 前面的1代表索引1,后面的1表示刷新标记
  • 正确写法:
    FlagIndex = 1
    RefreshFlag = 1
    table[FlagIndex] = RefresFlag

15.注意频繁调用函数参数量的控制

  • 推荐写法
    最好不要超过4个参数

16.优先使用static函数导出,减少使用成员方法导出
道理大家都懂

17.控制成员方法导出数量
不必要导出的函数用[NoToLuaAttribute]标记,减少lua虚拟机注册的函数数量

  • 正确写法
    [NoToLuaAttribute]
    public void InsertBuffObj(MBuff buff, bool isTarget)
    {

}

18.使用哈希值而不是字符串
针对Animator、Material或Shader属性进行内部寻址,推荐采用哈希的写法,例如Animator.StringToHash、Shader.PropertyToID,项目中写字符串都要谨慎,能不写就不写

  • 错误写法
    void Start()
    {
    anim = GetComponent();
    anim.SetFloat(“Speed”, 1.0f);
    }
  • 正确写法
    void Start()
    {
    anim = GetComponent();
    // 将字符串参数名转换为哈希值
    int speedHash = Animator.StringToHash(“Speed”);
    // 然后可以使用这个哈希值来设置参数值
    anim.SetFloat(speedHash, 1.0f);
    }

19.命名规范

  • 不推荐使用2、4命名,可以用To、For
  • 不要使用拼音,用最容易想到的单词

20.同时设置坐标和旋转

  • 推荐写法
    Transform.SetPositionAndRotation
    GameObject.Instantiate(prefab, parent, position, rotation)
  • 不推荐写法
    分开设置好几次属性

21.物理检测使用NoAlloc接口

  • 错误写法
    var layerMask = ~0;
    if (Physics.Raycast(transform.position, transform.forward, m_Results, Mathf.Infinity, layerMask) > 0)
    {

}
Physics2D. CircleCast();

  • 正确写法
    var layerMask = ~0;
    if (Physics.RaycastNonAlloc(transform.position, transform.forward, m_Results, Mathf.Infinity, layerMask) > 0)
    {

}
Physics2D. CircleCastNonAlloc();

22.数据配置

  • 表格数据配置不要使用拼接的方式在运行时分割
  • 数据存储不要采用json、xml等配置,推荐使用二进制或者scriptable object

23.不要出现空的Unity事件

  • 错误写法:
    void Update()
    {

}

24.尽可能减少每帧运行的代码
考虑代码是否必要每帧执行,如果不需要的话可以控制调用频率

  • 推荐写法
    private int interval = 3;

void Update()

if (Time.frameCount % interval == 0)

Function();

25.避免初始化函数处理太复杂的逻辑
例如Awake、Start、OnEnable中处理复杂的逻辑,增加启动加载时间

26.调用统一的日志接口
调用统一接口方便宏定义开关管理

  • 错误写法
    print(“这是一条日志”)
    Debug.log(“这是一条日志”)
  • 正确写法
    LogManager.Print(“这是一条日志”)

27.避免运行时添加组件
在运行时AddComponent需要一些开销,因为要检查是否重复组件或者需要其他依赖组件

  • 推荐做法
    采用预制件

28.vector距离判断
如果只是距离判断推荐使用

  • 不推荐写法
    Vector3.Magnitude(vec)
  • 推荐写法
    Vector3.SqrMagnitude(vec)

29.向量乘
向量乘需要耗时多一点,尽可能少的做向量运算

  • 不推荐写法
    Vector3 c = 3 * Vector3.one * 2;
  • 推荐写法
    Vector3 c = 3 * 2 * Vector3.one;

30.shader数据精度
能用小的就不用大的
Float(32bit) (位置与纹理坐标信息)
Half(16bit)(纹理坐标与颜色信息)
Fixed(11bit)(颜色信息)(SRP下不支持)

31.除法转换成乘倒数
频繁的做除法运算,可以改成乘以倒数,属于指令集优化,硬件上乘以倒数比除执行效率高

  • 不推荐写法
    var res = a / b;
    Var res = a / 5f;
  • 推荐写法
    var res = a * (1 / b);
    var res = a * 0.2f;
    除法运算性能开销时间是乘法的100倍(官方说法)

32.双重循环
不得已要双重循环,大循环在内部

  • 不推荐写法
    for (int i = 0; i < numTests; i++)
    {
    for (int j = 0; j < 2; j++)
    {
    int k = i * j;
    }
    }
  • 推荐写法
    for (int i = 0; i < 2; i++)
    {
    for (int j = 0; j < numTests; j++)
    {
    int k = i * j;
    }
    }
    [图片]

33.string操作
多次字符串操作,推荐使用stringbuilder,避免new string产生gc,如果stringbuilder能确定长度,最好先指定长度,也可以用ZString库

34.struct替代class
能用struct的就用struct,实在用不了再考虑class

35.系统自带默认值
例如Quaternion,Vector3等默认值全局静态只读变量,全局只有一个,减少一次new的操作

  • 不推荐写法
    Vector3 v3 = new Vector3(0, 0, 0);
  • 推荐写法
    Vector3 v3 = Vector3.zero;

37.避免装拆箱
装箱:值类型->引用类型
int value = 11;
object obj = value;
拆箱:引用类型->值类型
object obj = 42;
int value = (int)obj;

  • 不推荐写法
    循环内部产生拆箱、装箱的写法

38.正确使用对应的数据结构
大量的数据查询操作

  • 错误写法
    for(int i=0; i<1000; i++)
    {
    if(list.Contains(i))
    {
    //todo
    }
    }
    还有不要用for循环遍历查找luatable的数组表(数据量比较大)
  • 正确写法
    用HashSet结构或者lua哈希表

39.循环修改值

  • 错误写法
    foreach(var item in array)
    {
    if(item.id == xxx)
    {
    item.xxx = xxx
    }
    }
  • 正确写法
    for(int i = 0;i < array.count; i++)
    {
    if(array[i].id == xxx)
    {
    array[i].xxx = xxx
    }
    }

40.循环删除
倒序删除

  • 推荐写法
    // 假设有一个List
    List myList = new List { 1, 2, 3, 4, 5 };

// 倒序删除所有偶数元素
for (int i = myList.Count - 1; i >= 0; i–) {
if (myList[i] % 2 == 0) {
myList.RemoveAt(i);
}
}

  • 不推荐写法
    正序删除

41.获取组件
在Unity中,GetComponent方法有三种重载方式:GetComponent()、GetComponent(Type type)和GetComponent(string type)。根据搜索结果,推荐使用泛型方式GetComponent()

  • 推荐写法
    GetComponent()
    效率最高,耗时最少

42.跳过当前帧

  • 不推荐写法
    yield return new WaitForFrame();
  • 推荐写法
    yield return null;
    yield return 0;

43.代码编写原则

  • 推荐原则
    • 尽早返回:如果函数在执行过程中遇到可以立即确定结果的条件,应立即返回,而不是继续执行更多代码才返回
    • 避免深层嵌套:减少代码嵌套层数,可以提取函数,使用循环控制语句等方式来实现,警惕递归调用
    • 单一出口点:尽量让函数只有一个返回点,减少代码复杂性,使得函数执行流程更加清晰
    • 函数职责单一:确保一个函数只做一件事情,减少函数内部复杂度和耦合度,使得函数更容易理解和维护,功能通过若干单一函数组装
    • 使用断言:检测函数参数的合法有效性,如果不合法则立即抛出异常或返回

44.避免单帧大循环逻辑处理
CPU瞬时压力过大,造成Big Jank

  • 不推荐写法
    for(int i = 0;i < 100;i ++)
    {

}

  • 推荐策略
    • 分帧处理
    • 算法处理,例如二叉树、四叉树、AOI等算法策略

45.碰撞检测

  • 不推荐写法
    collider碰撞检测
  • 推荐写法
    vector距离检测

46.异步编程

  • 不推荐写法
    void Start()
    {
    StartCoroutine(DelayedAction());
    }

// 协程方法,延迟2秒后执行
IEnumerator DelayedAction()
{
yield return new WaitForSeconds(2.0f); // 等待2秒
Debug.Log(“Delayed action executed after 2 seconds”);
}

  • 推荐写法
    async void Start()
    {
    await Task.Delay(2000);
    }

47.lua代码变量

  • 错误写法
    都写成全局变量

48.扩展原方法/类
扩展模板代码方法,或者引擎内部方法

  • 错误写法
    生成模板代码直接生成class
  • 正确写法
    • 生成partial class的模板代码方法,以便提供扩展能力
    • 使用扩展方法

49.谨慎频繁调用强制垃圾回收

  • 不推荐写法
    定期执行Resources.UnUsedAssets() 或者lua的collectgarbage(“collect”)
  • 推荐写法
    • 资源采用引用计数管理和释放,不需要频繁粗暴调用Resources.UnUsedAssets(),会造成很大的帧率jank
    • 手动在合适的时机调用gc方法

50.向量浮点数运算

  • 不推荐写法
    Vector3 pos1 = new Vector3(1,2,3);
    Vector3 pos2 = new Vector3(4,5,6);
    var pos3 = pos1 + pos2;
    var pos4 = pos1 * 2f;
  • 推荐写法
    var pos3 = new Vector3(pos1.x + pos2.x, pos1.y + pos2.y, …);
    var pos2 = new Vector3(pos1.x * 2f, pos1.y * 2f, …);

51.lua UI变量查找

  • 不推荐写法
    find的方式
  • 推荐写法
    Editor中Prefab绑定,lua使用

52.tag比较

  • 不推荐写法
    gameObject.Tag == “MyTag”
    属性访问,返回一个字符串跟另外一个字符串比较
  • 推荐写法
    gameObject.CompareTag(“MyTag”)
    实用CompareTag方法,内部直接比较,避免一次字符串创建

53.能用localposition就不要用position
原因:获取position背后会做一系列计算,因为涉及到全局坐标,需求中能用localposition就用这个

持续更新,欢迎补充…


http://www.kler.cn/a/414065.html

相关文章:

  • Linux八股积累与笔记
  • 工业AI质检 AI质检智能系统 尤劲恩(上海)信息科技有限公司
  • Web 毕设篇-适合小白、初级入门练手的 Spring Boot Web 毕业设计项目:电影院后台管理系统(前后端源码 + 数据库 sql 脚本)
  • 【机器学习】—PCA(主成分分析)
  • 简释下oracle的set define的使用场景
  • Unity3D 截图
  • 达梦docker版本数据库 重新初始化实例--比如大小写敏感参数设置不生效
  • Laravel8.5+微信小程序实现京东商城秒杀方案
  • SQL面试题——in和not in 不支持怎么办
  • 深度学习day6- 损失函数和BP算法1
  • 【从零开始的LeetCode-算法】3304. 找出第 K 个字符 I
  • 分层架构 IM 系统之 Entry 设计实现
  • 跨境电商搭建知识库能带来什么效益?
  • 栈和队列——考研笔记
  • 【论文复现】BERT论文解读及情感分类实战
  • 页面内容下载为pdf
  • 2、Three.js初步认识场景Scene、相机Camera、渲染器Renderer三要素
  • k8s网络服务
  • 【数据仓库 | Data Warehouse】数据仓库的四大特性
  • 为什么redis用跳表不用b+树,而mysql用b+树而不是跳表?
  • CTF之密码学(埃特巴什码 )
  • (0基础保姆教程)-JavaEE开课啦!--12课程(Spring MVC注解 + Vue2.0 + Mybatis)-实验10
  • Python 删除Word中的表格
  • Qt中CMakeLists.txt解释大全
  • Django Admin与Vue前后端分离开发实战教程
  • open-instruct - 训练开放式指令跟随语言模型