
Unity 小知识 大智慧
🎬 博客主页:https://xiaoy.blog.csdn.net
🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN🙉
🎄 学习专栏推荐:Unity系统学习专栏
🌲 游戏制作专栏推荐:游戏制作
🌲Unity实战100例专栏推荐:Unity 实战100例 教程
🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📆 未来很长,值得我们全力奔赴更美好的生活✨
------------------❤️分割线❤️-------------------------

Unity 实用技巧学习
Unity常用属性底层开销速查 + gameObject.name完整原理笔记
说明:MonoBehaviour下直接写
name等价gameObject.name;transform.position是高频踩坑点。
跨桥:C#托管 ↔ Unity C++原生引擎;GC分配=托管堆产生对象,会给GC带来压力,移动端敏感。
一、常见属性底层开销对比速查表
| 属性 | 底层实现 | 跨桥调用 | GC Alloc(get读取) | 注意要点 |
|---|---|---|---|---|
gameObject.name | C#是包装属性,真实字符串存储在C++GameObject;每次get:跨桥拷贝C++字符串,新建C# string对象 | ✅每次访问都跨桥 | ✅每次读取都分配string GC | Update/循环严禁频繁读取;运行时不变就Awake缓存成员变量;无原生无GC对比接口 |
gameObject.tag | tag字符串存C++;get同样跨桥生成新string | ✅每次访问都跨桥 | ✅每次读取分配string GC | 判断标签不要用 ==,使用gameObject.CompareTag("xxx")无GC |
transform.position | 属性,get:跨桥读取C++变换矩阵,计算世界坐标返回新Vector3结构体 | ✅每次访问都跨桥 | ❌无GC(struct值类型) | 频繁读写有CPU开销;Update大量物体反复读写会耗CPU;不要多次重复get,优先缓存局部变量 |
transform.localPosition | 属性,get:跨桥读取C++本地变换,返回新Vector3 | ✅每次访问都跨桥 | ❌无GC(struct值类型) | 同上;set同样跨桥;连续多次读写建议先缓存到局部Vector3 |
transform.rotation | 跨桥读取四元数,返回新Quaternion | ✅每次访问都跨桥 | ❌无GC | 值类型无堆分配,但跨桥CPU开销存在 |
transform.localRotation | 跨桥读取本地四元数 | ✅每次访问都跨桥 | ❌无GC | 同上 |
transform.localScale | 跨桥读取缩放Vector3 | ✅每次访问都跨桥 | ❌无GC | 同上 |
gameObject.GetInstanceID() | 跨桥返回int数字 | ✅跨桥 | ❌无GC | 适合运行时区分对象,不要用name做对象身份判断 |
gameObject.activeSelf | 跨桥读取激活状态bool | ✅跨桥 | ❌无GC | 仅bool返回,开销很小,可适度频繁调用 |
重要区分:
- Vector3/Quaternion是struct值类型:每次get返回新副本,CPU跨桥开销,没有GC堆分配;
- name/tag返回string引用类型:每次get都会在托管堆新建字符串,既有跨桥CPU开销,又产生GC垃圾,危害更大。
二、Unity组件访问 gameObject.name 底层原理完整笔记
1.底层执行流程(每一次get读取都会完整走一遍)
gameObject.name是C#层Native属性,C# GameObject只是薄包装句柄(存实例指针/InstanceID),C#侧不存储名字字符串,真正名字保存在C++原生GameObject。
- C#访问
gameObject.name,触发托管→原生跨桥marshalling; - 引擎C++根据句柄找到原生GameObject实例,取出
char*名字; - 将C++字符串拷贝,在C#托管堆创建全新string对象返回;
- C#拿到字符串,若无引用很快变为GC垃圾。
简写
this.name(MonoBehaviour直接写name)完全等价gameObject.name,底层开销一模一样。
set赋值
gameObject.name="xxx":C#字符串封送拷贝到C++修改原生名字;C#依然不缓存,下一次读取依旧重新生成新string。
2.两大性能开销
- CPU开销:C#与C++跨边界调用
单次开销很小,Update、循环、大量实体频繁访问会累积CPU耗时。 - GC堆分配(最主要危害)
string是不可变引用类型,每读取一次就分配一次托管堆内存。Update每帧读取会持续产生GC Alloc,移动端容易出现GC卡顿。
Profiler → GC Allocated 面板可以直接观测到字节分配。
⚠️tag同理;但是tag提供CompareTag(),直接在C++对比内部标识,零GC;name没有官方无GC替代接口。
3.代码示例:好坏写法对比
❌错误:Update每帧读取,持续GC分配
void Update()
{
Debug.Log(gameObject.name); //每帧跨桥+新建string,持续产生垃圾
}
误区:赋值给局部变量并不能消除分配
void Update()
{
string temp = gameObject.name; //get依旧执行,依然分配GC,只是临时接收结果
}
✅正确:Awake/Start一次性缓存成员字段(名字运行时不变)
private string _cachedObjName;
void Awake()
{
_cachedObjName = gameObject.name; //仅执行一次跨桥、一次分配
}
void Update()
{
Debug.Log(_cachedObjName); //读取本地变量:0跨桥、0GC
}
如果运行时代码会修改
gameObject.name,修改之后同步更新缓存变量。
✅业务身份判断不要用name字符串对比
❌不推荐:if(gameObject.name == "Enemy") 每次访问name带来GC
✅推荐方案:
gameObject.CompareTag("Enemy")- 自定义标记MonoBehaviour组件
gameObject.GetInstanceID()(int无GC,区分对象实例)- 枚举ID配置
4.允许 / 严禁使用场景
✅安全场景:Awake、Start、OnEnable、只执行一次初始化逻辑、编辑器脚本,偶尔访问无压力。
❌禁止场景:Update / FixedUpdate / LateUpdate、for/while循环、OnTriggerStay、大量怪物/子弹等实体每帧读取name。
5.常见误区澄清
- IL2CPP打包不会消除name读取的字符串堆分配,只是编译方式改变,C++字符串封送拷贝逻辑保留。
transform.name底层转发gameObject.name,开销完全相同。=只是局部变量接收返回值,不能消除getter内部的跨桥和内存分配;只有提前缓存到成员字段才生效。
补充小知识点
gameObject.tag判断标签务必用CompareTag(),不要gameObject.tag == "xxx";后者每次读取tag产生string GC。- position/localPosition虽然没有GC,但大量循环重复读取会累积CPU开销,建议缓存局部变量:
Vector3 pos = transform.position;
pos.x += 1;
transform.position = pos;
面试背诵精简版
gameObject.name字符串存在C++层,C#只是包装句柄;每次get触发跨桥,拷贝生成全新C# string,产生GC。- 不要在Update频繁读name;名字不变就在Awake缓存成员变量。
- name没有无GC对比API;tag判断优先CompareTag。
- transform.position等值类型属性:跨桥CPU开销,无GC;多次读写缓存局部变量优化。
资料白嫖,技术互助
| 学习路线指引(点击解锁) | 知识定位 | 人群定位 |
|---|---|---|
| 🧡 Unity系统学习专栏 | 入门级 | 本专栏从Unity入门开始学习,快速达到Unity的入门水平 |
| 💛 Unity实战类项目 | 进阶级 | 计划制作Unity的 100个实战案例!助你进入Unity世界,争取做最全的Unity原创博客大全。 |
| ❤️ 游戏制作专栏 | 难度偏高 | 分享学习一些Unity成品的游戏Demo和其他语言的小游戏! |
| 💚 游戏爱好者万人社区 | 互助/吹水 | 数万人游戏爱好者社区,聊天互助,白嫖奖品 |
| 💙 Unity100个实用技能 | Unity查漏补缺 | 针对一些Unity中经常用到的一些小知识和技能进行学习介绍,核心目的就是让我们能够快速学习Unity的知识以达到查漏补缺 |

转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/zhangay1998/article/details/164450376




