关于 unity-csharp
unity-csharp 面向编写和优化 Unity C# 代码的开发者,涵盖 VR 与移动项目。它结合组件和生命周期模式、性能优化实践及分析指导。
主要功能
- 01MonoBehaviour 生命周期与组件设计
- 02缓存、事件、协程和空值安全
- 03ScriptableObject 数据模式
- 04绘制调用、批处理与 LOD 优化
- 05VR 和移动端性能分析指导
适用场景
- 01围绕专注型 Unity 组件组织脚本
- 02减少重复组件查询和 Update 工作
- 03优化 VR 或移动端目标的渲染
- 04分析 CPU、内存和绘制调用问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ alexanderstephenthompson/ claude-hub --skill unity-csharp安装后在 Claude Code 中输入/unity-csharp确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/component-checklist.mdvr-performance-limits.mdreferences/lifecycle.mdprofiling.md详细介绍
Unity C# 技能
版本: 2.0 技术栈: Unity、C#
编写整洁、高性能 Unity C# 代码的模式。包含 VR/移动端优化。
范围和边界
此技能涵盖:
- MonoBehaviour 生命周期和组件架构
- Unity 专用 C# 模式(缓存、事件、协程、空值安全)
- 性能优化(绘制调用、批处理、LOD、对象池)
- VR/移动端性能目标和性能分析
- ScriptableObject 的用法
交由其他技能处理:
vrc-udon:VRChat 专用脚本(UdonSharp)vrc-worlds:VRChat 世界设置和限制vrc-avatars:VRChat 头像设置和限制
适用场景: 为 Unity 编写 C# 脚本,或优化 Unity 的 VR/移动端性能。
核心原则
- 组合优于继承 — 使用小而专注的组件。
- 尽可能避免 Update() — 改用事件驱动机制或协程。
- 缓存引用 — GetComponent 开销较大,应在 Awake 中缓存。
- 使用 ScriptableObject 存储数据 — 将数据与行为解耦。
- 空值安全访问 — Unity 对象随时可能被销毁。
- 先测量 — 优化前先进行性能分析;直觉并不可靠。
- 积极进行批处理 — 相同材质有助于批处理。在 VR 中,绘制调用最重要。
模式
引用缓存
public class PlayerController : MonoBehaviour
{
private Rigidbody _rb;
private Animator _animator;
[SerializeField] private Transform _cameraTarget;
private void Awake()
{
_rb = GetComponent<Rigidbody>();
_animator = GetComponent<Animator>();
}
private void FixedUpdate()
{
_rb.AddForce(Vector3.up);
}
}
事件系统(ScriptableObject)
[CreateAssetMenu(menuName = "Events/Game Event")]
public class GameEvent : ScriptableObject
{
private readonly List<GameEventListener> _listeners = new();
public void Raise()
{
for (int i = _listeners.Count - 1; i >= 0; i--)
_listeners[i].OnEventRaised();
}
public void RegisterListener(GameEventListener listener) =>
_listeners.Add(listener);
public void UnregisterListener(GameEventListener listener) =>
_listeners.Remove(listener);
}
public class GameEventListener : MonoBehaviour
{
[SerializeField] private GameEvent _event;
[SerializeField] private UnityEvent _response;
private void OnEnable() => _event.RegisterListener(this);
private void OnDisable() => _event.UnregisterListener(this);
public void OnEventRaised() => _response.Invoke();
}
空值安全模式
// Unity overloads == for destroyed objects
if (_target != null)
{
_target.DoSomething();
}
// Best: explicit destroyed check
if (_target != null && !_target.Equals(null))
{
_target.DoSomething();
}
协程模式
private IEnumerator FadeOut(float duration)
{
float elapsed = 0f;
Color startColor = _renderer.material.color;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
_renderer.material.color = Color.Lerp(startColor, Color.clear, t);
yield return null;
}
gameObject.SetActive(false);
}
对象池
public class ObjectPool : MonoBehaviour
{
[SerializeField] private GameObject _prefab;
[SerializeField] private int _initialSize = 10;
private Queue<GameObject> _pool = new();
private void Awake()
{
for (int i = 0; i < _initialSize; i++)
{
var obj = Instantiate(_prefab);
obj.SetActive(false);
_pool.Enqueue(obj);
}
}
public GameObject Get()
{
if (_pool.Count == 0)
return Instantiate(_prefab);
var pooled = _pool.Dequeue();
pooled.SetActive(true);
return pooled;
}
public void Return(GameObject obj)
{
obj.SetActive(false);
_pool.Enqueue(obj);
}
}
静态批处理
gameObject.isStatic = true;
// Or specific flags
GameObjectUtility.SetStaticEditorFlags(gameObject,
StaticEditorFlags.BatchingStatic |
StaticEditorFlags.OcclusionStatic);
VR 性能目标
| 指标 | Quest 2 | Quest 3 | PC VR |
|---|---|---|---|
| 绘制调用 | <100 | <150 | <200 |
| 三角形数 | <100K | <150K | <1M |
| 帧时间 | <14ms(72fps) | <11ms(90fps) | <11ms(90fps) |
| 纹理内存 | <200MB | <500MB | <1GB |
LOD 配置
LOD 0: 100% triangles (0-10m)
LOD 1: 50% triangles (10-25m)
LOD 2: 25% triangles (25-50m)
Culled: 0 triangles (50m+)
材质图集
Before: 20 objects x 20 materials = 20 draw calls (no batching)
After: 20 objects x 1 atlas material = 1 draw call (batched)
反模式
| 反模式 | 问题 | 修复方法 |
|---|---|---|
在 Update 中调用 GetComponent | 每帧都产生较大开销 | 在 Awake 中缓存 |
Find 或 FindObjectOfType | 速度慢、不可靠 | 注入引用或使用事件 |
| Update 循环开销过大 | 消耗性能 | 使用事件、协程或 FixedUpdate |
| 用字符串比较标签 | 容易拼写错误且速度慢 | 使用 CompareTag 或常量 |
| 所有字段都设为 Public | 缺少封装 | 使用 [SerializeField] private |
| 每个对象都使用独立材质 | 无法进行批处理 | 共享材质并使用图集 |
| 没有 LOD | 任意距离下都渲染完整细节 | 添加 LOD 组 |
| 在游戏运行时 Instantiate/Destroy | GC 峰值、卡顿 | 使用对象池 |
| 到处使用实时光照 | 阴影开销大 | 烘焙光照并限制实时光照 |
| 没有遮挡剔除 | 渲染被遮挡的对象 | 烘焙遮挡数据 |
检查清单
代码质量
- 引用已在 Awake 中缓存
- Update/FixedUpdate 中没有 GetComponent
- 运行时代码中没有 Find 方法
- 共享数据使用 ScriptableObject
- 使用事件实现解耦通信
- 对可销毁对象进行空值检查
性能
- 静态对象已标记为 static
- 尽可能共享材质
- 小型道具使用纹理图集
- 重要网格已设置 LOD 组
- 已烘焙遮挡剔除
- 为生成的对象使用对象池
- 已烘焙光照(并非全部使用实时光照)
性能分析
- 使用 Frame Debugger 检查绘制调用
- 运行 Profiler 检查 CPU 峰值
- 使用 Memory Profiler 检查内存泄漏
- 已在目标设备上测试(而不只是编辑器)
参考资料
references/lifecycle.md— MonoBehaviour 生命周期和执行顺序references/profiling.md— Unity Profiler 的用法和结果解读
资源
assets/component-checklist.md— Unity 组件设计检查清单assets/vr-performance-limits.md— VR 平台性能限制和目标
评论
支持 Markdown