mcpField
U
unity-csharp

unity-csharp

github.com/alexanderstephenthompson开发70 次安装
代码仓库

关于 unity-csharp

unity-csharp 面向编写和优化 Unity C# 代码的开发者,涵盖 VR 与移动项目。它结合组件和生命周期模式、性能优化实践及分析指导。

主要功能

  1. 01MonoBehaviour 生命周期与组件设计
  2. 02缓存、事件、协程和空值安全
  3. 03ScriptableObject 数据模式
  4. 04绘制调用、批处理与 LOD 优化
  5. 05VR 和移动端性能分析指导

适用场景

  1. 01围绕专注型 Unity 组件组织脚本
  2. 02减少重复组件查询和 Update 工作
  3. 03优化 VR 或移动端目标的渲染
  4. 04分析 CPU、内存和绘制调用问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/alexanderstephenthompson/claude-hub --skill unity-csharp

安装后在 Claude Code 中输入/unity-csharp确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
component-checklist.md
vr-performance-limits.md
references/
lifecycle.md
profiling.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/移动端性能。


核心原则

  1. 组合优于继承 — 使用小而专注的组件。
  2. 尽可能避免 Update() — 改用事件驱动机制或协程。
  3. 缓存引用 — GetComponent 开销较大,应在 Awake 中缓存。
  4. 使用 ScriptableObject 存储数据 — 将数据与行为解耦。
  5. 空值安全访问 — Unity 对象随时可能被销毁。
  6. 先测量 — 优化前先进行性能分析;直觉并不可靠。
  7. 积极进行批处理 — 相同材质有助于批处理。在 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 2Quest 3PC 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/DestroyGC 峰值、卡顿使用对象池
到处使用实时光照阴影开销大烘焙光照并限制实时光照
没有遮挡剔除渲染被遮挡的对象烘焙遮挡数据

检查清单

代码质量
  • 引用已在 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