unity-csharp
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUnity C# Skill
Unity C# 技能指南
Version: 2.0
Stack: Unity, C#
Patterns for writing clean, performant Unity C# code. Includes VR/mobile optimization.
Version: 2.0
Stack: Unity, C#
编写整洁、高性能Unity C#代码的设计模式,包含VR/移动端优化内容。
Scope and Boundaries
适用范围与边界
This skill covers:
- MonoBehaviour lifecycle and component architecture
- Unity-specific C# patterns (caching, events, coroutines, null safety)
- Performance optimization (draw calls, batching, LODs, pooling)
- VR/mobile performance targets and profiling
- ScriptableObject usage
Defers to other skills:
- : VRChat-specific scripting (UdonSharp)
vrc-udon - : VRChat world setup and limits
vrc-worlds - : VRChat avatar setup and limits
vrc-avatars
Use this skill when: Writing C# scripts for Unity, or optimizing Unity performance for VR/mobile.
本技能涵盖:
- MonoBehaviour生命周期与组件架构
- Unity专属C#设计模式(缓存、事件、协程、空值安全)
- 性能优化(绘制调用、批处理、LOD、对象池)
- VR/移动端性能指标与性能分析
- ScriptableObject的使用
以下内容请参考其他技能文档:
- : VRChat专属脚本开发(UdonSharp)
vrc-udon - : VRChat世界搭建与限制
vrc-worlds - : VRChat Avatar搭建与限制
vrc-avatars
适用场景: 编写Unity的C#脚本,或针对VR/移动端优化Unity性能时。
Core Principles
核心原则
- Composition Over Inheritance — Small, focused components.
- Avoid Update() When Possible — Event-driven or coroutines instead.
- Cache References — GetComponent is expensive; cache in Awake.
- ScriptableObjects for Data — Decouple data from behavior.
- Null-Safe Access — Unity objects can be destroyed at any time.
- Measure First — Profile before optimizing; gut feelings lie.
- Batch Aggressively — Same material = potential batch. Draw calls matter most in VR.
- 组合优于继承 — 使用小巧、职责单一的组件。
- 尽可能避免使用Update() — 优先采用事件驱动或协程。
- 缓存引用 — GetComponent开销较大,应在Awake中缓存。
- 使用ScriptableObject存储数据 — 实现数据与逻辑解耦。
- 空值安全访问 — Unity对象可能随时被销毁。
- 先测量再优化 — 优化前先进行性能分析,直觉不可靠。
- 积极批处理 — 相同材质可进行批处理,绘制调用对VR性能影响最大。
Patterns
设计模式示例
Reference Caching
引用缓存模式
csharp
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);
}
}csharp
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);
}
}Event System (ScriptableObject)
事件系统(基于ScriptableObject)
csharp
[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();
}csharp
[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();
}Null-Safe Pattern
空值安全模式
csharp
// Unity overloads == for destroyed objects
if (_target != null)
{
_target.DoSomething();
}
// Best: explicit destroyed check
if (_target != null && !_target.Equals(null))
{
_target.DoSomething();
}csharp
// Unity为已销毁对象重载了==运算符
if (_target != null)
{
_target.DoSomething();
}
// 最佳实践:显式检查销毁状态
if (_target != null && !_target.Equals(null))
{
_target.DoSomething();
}Coroutine Pattern
协程模式
csharp
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);
}csharp
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);
}Object Pooling
对象池模式
csharp
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);
}
}csharp
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);
}
}Static Batching
静态批处理
csharp
gameObject.isStatic = true;
// Or specific flags
GameObjectUtility.SetStaticEditorFlags(gameObject,
StaticEditorFlags.BatchingStatic |
StaticEditorFlags.OcclusionStatic);csharp
gameObject.isStatic = true;
// 或设置特定标记
GameObjectUtility.SetStaticEditorFlags(gameObject,
StaticEditorFlags.BatchingStatic |
StaticEditorFlags.OcclusionStatic);VR Performance Targets
VR性能指标
| Metric | Quest 2 | Quest 3 | PC VR |
|---|---|---|---|
| Draw Calls | <100 | <150 | <200 |
| Triangles | <100K | <150K | <1M |
| Frame Time | <14ms (72fps) | <11ms (90fps) | <11ms (90fps) |
| Texture Memory | <200MB | <500MB | <1GB |
| 指标 | Quest 2 | Quest 3 | PC VR |
|---|---|---|---|
| 绘制调用 | <100 | <150 | <200 |
| 三角面数 | <100K | <150K | <1M |
| 帧时间 | <14ms (72fps) | <11ms (90fps) | <11ms (90fps) |
| 纹理内存 | <200MB | <500MB | <1GB |
LOD Configuration
LOD配置
LOD 0: 100% triangles (0-10m)
LOD 1: 50% triangles (10-25m)
LOD 2: 25% triangles (25-50m)
Culled: 0 triangles (50m+)LOD 0: 100%三角面数(0-10米)
LOD 1: 50%三角面数(10-25米)
LOD 2: 25%三角面数(25-50米)
剔除: 0三角面数(50米以外)Material Atlasing
材质图集优化
Before: 20 objects x 20 materials = 20 draw calls (no batching)
After: 20 objects x 1 atlas material = 1 draw call (batched)优化前:20个对象 × 20种材质 = 20次绘制调用(无法批处理)
优化后:20个对象 × 1种图集材质 = 1次绘制调用(可批处理)Anti-Patterns
反模式
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Expensive every frame | Cache in Awake |
| Slow, fragile | Inject references or use events |
| Heavy Update loops | Performance drain | Use events, coroutines, or FixedUpdate |
| String comparisons for tags | Typo-prone, slow | Use CompareTag or constants |
| Public fields for everything | No encapsulation | Use [SerializeField] private |
| Unique material per object | No batching possible | Share materials, use atlases |
| No LODs | Full detail at any distance | Add LOD groups |
| Instantiate/Destroy in gameplay | GC spikes, stutters | Object pooling |
| Realtime lights everywhere | Expensive shadows | Bake lighting, limit realtime |
| No occlusion culling | Render hidden objects | Bake occlusion data |
| 反模式 | 问题 | 修复方案 |
|---|---|---|
| 每帧开销大 | 在Awake中缓存引用 |
使用 | 速度慢、易出错 | 注入引用或使用事件 |
| 繁重的Update循环 | 性能消耗大 | 使用事件、协程或FixedUpdate |
| 使用字符串比较标签 | 易拼写错误、速度慢 | 使用CompareTag或常量 |
| 所有字段设为Public | 无封装性 | 使用[SerializeField] private |
| 每个对象使用唯一材质 | 无法进行批处理 | 共享材质、使用材质图集 |
| 未使用LOD | 任何距离都渲染全细节 | 添加LOD组 |
| 游戏运行时调用Instantiate/Destroy | GC突增、卡顿 | 使用对象池 |
| 大量使用实时光照 | 阴影开销大 | 烘焙光照、限制实时光照数量 |
| 未启用遮挡剔除 | 渲染隐藏对象 | 烘焙遮挡数据 |
Checklist
检查清单
Code Quality
代码质量
- References cached in Awake
- No GetComponent in Update/FixedUpdate
- No Find methods in runtime code
- ScriptableObjects for shared data
- Events for decoupled communication
- Null checks for destroyable objects
- 已在Awake中缓存引用
- 未在Update/FixedUpdate中调用GetComponent
- 运行时代码中未使用Find方法
- 已使用ScriptableObject存储共享数据
- 已使用事件实现解耦通信
- 对可销毁对象进行了空值检查
Performance
性能优化
- Static objects marked static
- Materials shared where possible
- Texture atlases for small props
- LOD groups on significant meshes
- Occlusion culling baked
- Object pooling for spawned objects
- Lighting baked (not all realtime)
- 静态对象已标记为Static
- 尽可能共享材质
- 小道具已使用纹理图集
- 重要网格已添加LOD组
- 已烘焙遮挡剔除数据
- 生成对象已使用对象池
- 光照已烘焙(未全部使用实时光照)
Profiling
性能分析
- Frame Debugger checked for draw calls
- Profiler run for CPU spikes
- Memory Profiler checked for leaks
- Tested on target device (not just editor)
- 已使用Frame Debugger检查绘制调用
- 已使用Profiler排查CPU峰值
- 已使用Memory Profiler检查内存泄漏
- 已在目标设备测试(而非仅编辑器)
References
参考资料
- — MonoBehaviour lifecycle and execution order
references/lifecycle.md - — Unity Profiler usage and interpretation
references/profiling.md
- — MonoBehaviour生命周期与执行顺序
references/lifecycle.md - — Unity Profiler使用与解读
references/profiling.md
Assets
相关资源
- — Unity component design checklist
assets/component-checklist.md - — VR platform performance limits and targets
assets/vr-performance-limits.md
- — Unity组件设计检查清单
assets/component-checklist.md - — VR平台性能限制与指标
assets/vr-performance-limits.md