mcpField
U
unity-csharp-scripting

unity-csharp-scripting

github.com/gamedev-skills开发5.4k6.6k 次安装今天更新
代码仓库

关于 unity-csharp-scripting

面向 Unity 6 开发者的 C# 脚本参考技能,用于编写或修复 MonoBehaviour。重点涵盖生命周期回调、组件访问、Inspector 序列化、协程及项目检查。

主要功能

  1. 01MonoBehaviour 生命周期指导
  2. 02缓存组件引用
  3. 03适用于 Inspector 的序列化
  4. 04使用协程编写定时逻辑
  5. 05.meta 与 GUID 项目检查

适用场景

  1. 01为游戏行为选择回调
  2. 02向设计师开放可调字段
  3. 03实现延迟和顺序动作
  4. 04文件变更后检查资源引用

安装

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

$npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill unity-csharp-scripting

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

附带文件references/lifecycle-and-coroutines.mdscripts/check_unity_project.py

详细介绍

Unity C# 脚本编写(MonoBehaviour)

在 Unity 6 中编写正确、符合惯例的游戏脚本。正确处理生命周期、组件访问、序列化和协程,让行为保持确定性,并让 Inspector 持续发挥作用。目标版本为 Unity 6.3 LTS (6000.3),使用 C# / .NET Standard 2.1。

适用场景

  • 编写或修复 MonoBehaviour 时使用:选择合适的生命周期回调、读取或缓存组件、向 Inspector 暴露字段,或使用协程运行定时逻辑。
  • 项目包含 *.cs 文件、Assembly-CSharp 或 *.asmdef,以及 ProjectSettings/ 文件夹时使用。

不适用场景: 移动刚体 / 处理碰撞响应 → unity-physics;读取玩家输入 → unity-input-system;共享数据资源 / 配置 → unity-scriptableobjects;Animator 参数 → unity-animation。此技能负责脚本生命周期和 C# 接线逻辑,而非这些子系统。

核心工作流

  1. 根据用途选择回调,而不是依赖习惯。 Awake(缓存引用,在加载时运行一次)、OnEnable(订阅事件)、Start(依赖其他对象 Awake 的初始化)、Update(逐帧逻辑 / 输入轮询)、FixedUpdate(物理)、LateUpdate(在移动后跟随摄像机)、OnDisable/OnDestroy(取消订阅 / 清理)。
  2. 在 Awake 中缓存组件查找结果——切勿每帧调用 GetComponent。
  3. 使用 [SerializeField] private 暴露可调参数,而不是使用公共字段,这样其他代码无法修改它们,但设计师可以在 Inspector 中编辑。
  4. 在 Update 中,用 Time.deltaTime 缩放逐帧值(在 FixedUpdate 中会自动采用 Time.fixedDeltaTime 语义)。
  5. 使用协程编写按时间顺序执行的逻辑(延迟、补间、“执行 X,然后等待,再执行 Y”);使用 StartCoroutine 启动,并以确定的方式停止。
  6. 在 Play 模式下验证:检查 Console 中是否有空引用异常,确认 Inspector 中的值是否按预期更新;如果 Update 开销较高,则查看 Profiler。
  7. 完成前检查 .meta 文件。 在 Editor 外创建、移动、重命名或删除脚本或资源时,始终同时移动文件及其 .meta 文件(新建 .meta 会生成新的 GUID,导致引用失效)。然后运行 python scripts/check_unity_project.py <project-dir>(添加 --json 可输出机器可读结果),修复所有 ERROR(重复或格式错误的 GUID、移动文件时未一并移动 .meta、缺失的脚本引用),并重新运行,直到退出代码为 0。全新文件缺少 .meta 仅会产生警告;Unity 会在下次导入时创建该文件。请使用相对于此技能目录的脚本路径,或先解析已安装的技能路径。该脚本需要 Python 3.10+,不需要其他依赖,只会读取文本序列化资源,不能替代在 Unity 中打开项目。

模式

1. 生命周期与缓存组件(标准骨架)
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]      // auto-adds the dependency, prevents null refs
public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 6f;   // editable in Inspector, private in code
    private Rigidbody _rb;                            // cached, not fetched per frame

    private void Awake() => _rb = GetComponent<Rigidbody>();  // cache once on load

    private void Update()
    {
        // Per-frame, non-physics work. Scale by deltaTime so it is frame-rate independent.
        transform.Rotate(0f, 90f * Time.deltaTime, 0f);
    }

    private void FixedUpdate()
    {
        // Physics work belongs here (fixed timestep). See the unity-physics skill.
        _rb.MovePosition(_rb.position + transform.forward * moveSpeed * Time.fixedDeltaTime);
    }
}
2. 使用 TryGetComponent 安全访问组件
// Avoids allocating a null and is clearer than GetComponent + null check.
if (other.TryGetComponent<Health>(out var health))
    health.Apply(-10);
3. 在 Inspector 中正确显示的序列化方式
[SerializeField, Range(0f, 1f)] private float volume = 0.8f;  // slider
[SerializeField] private string playerName = "Hero";          // private but serialized

[System.Serializable]                 // REQUIRED for a plain class to serialize/show
public class Stats { public int hp = 100; public int mana = 50; }

[SerializeField] private Stats stats = new();  // nested struct-like data in the Inspector
4. 用协程编写按时间顺序执行的逻辑
private void Start() => StartCoroutine(FlashThenHide());

private System.Collections.IEnumerator FlashThenHide()
{
    yield return new WaitForSeconds(0.5f);   // wait half a second of game time
    GetComponent<Renderer>().enabled = false;
    yield return null;                       // resume next frame
}

常见陷阱

  • 在 Update 中调用 GetComponent——每帧都会搜索,严重影响性能。在 Awake/Start 中缓存引用。
  • 在 Update 中处理物理——在 FixedUpdate 之外对 Rigidbody 施加力或调用 MovePosition 会导致抖动,且行为取决于时间步长。在 Update 中读取输入,在 FixedUpdate 中应用物理。
  • 依赖不同对象的 Start 执行顺序——所有对象的 Awake 都运行后,才会运行 Start,但各个 Start 之间的顺序未定义。跨对象连接在 Start 中进行,自身设置在 Awake 中进行。
  • 仅为了在 Inspector 中显示字段而将它们声明为 public——这样也会允许任何脚本修改这些字段。改用 [SerializeField] private。
  • gameObject.tag == "Enemy" 会分配字符串,速度也更慢;请使用 gameObject.CompareTag("Enemy")。
  • 禁用 GameObject 时协程会停止——禁用对象的协程会被终止;如果协程必须在启用状态切换期间持续运行,请在 OnEnable 中重新调用 StartCoroutine。
  • Update 不会在 Start 之前运行,但第一次 Update 可能与 Start 在同一帧运行——如果以不寻常的方式拆分设置流程,请防范尚未初始化的字段。

参考资料

  • 有关完整的事件执行顺序表和高级协程模式(自定义 CustomYieldInstruction、按句柄停止、WaitUntil/WaitWhile),请阅读 references/lifecycle-and-coroutines.md。
  • scripts/check_unity_project.py——只读 .meta/GUID 和缺失脚本检查器;--help 会列出所有检查项和退出代码。
  • 主要文档:Unity 手册“事件函数执行顺序” (https://docs.unity3d.com/Manual/execution-order.html)以及 ScriptReference/MonoBehaviour。

相关技能

  • unity-physics——Rigidbody、碰撞和 FixedUpdate 运动。
  • unity-input-system——将玩家输入读入这些脚本。
  • unity-scriptableobjects——在脚本间共享数据/配置,无需使用单例。

评论

支持 Markdown