unity c# 脚本入门

Unity脚本需满足文件名类名一致、位于Assets下、继承MonoBehaviour三条件才能挂载;Update适合轻量逻辑但须缓存组件引用;GetComponent返回null主因是组件缺失或时机不当;协程不阻塞主线程,WaitForSeconds受时间缩放影响。

Unity C# 脚本不是“学完语法就能用”,而是得立刻知道 Start()Update() 什么时候该写什么、GetComponent() 为什么总返回 null、以及为什么拖了脚本到 GameObject 上却没反应——这些才是卡住新手的真问题。

脚本挂不上?先确认三个硬性条件

Unity 不会自动识别任意 C# 文件。必须同时满足:

  • 文件名和类名**完全一致**(包括大小写),例如文件叫 PlayerController.cs,类必须是 public class PlayerController : MonoBehaviour
  • 脚本必须放在 Assets 文件夹下(不能在 Project SettingsLibrary 里)
  • 脚本里至少继承 MonoBehaviour,且没有编译错误(看 Console 窗口有没有红色报错)

常见现象:拖脚本到 Inspector 却看不到组件——八成是类名/文件名不一致,或脚本在 PluginsEditor 子目录下(那些目录有特殊编译顺序)。

Update() 里写逻辑,但别在里面做耗时操作

Update() 每帧调用一次,适合处理输入、简单位移、状态轮询;但它不是“万能主循环”。容易踩的坑:

  • Update() 里频繁调用 GetComponent()FindGameObjectWithTag() —— 这些是运行时查找,开销大,应缓存到字段
  • 把资源加载(如 Resources.Load())、JSON 解析、路径计算等放进去 —— 会导致帧率骤降
  • 误以为 Update() 是“初始化入口”——实际初始化逻辑应该写在 Start()Awake()

正确做法:

public class PlayerController : MonoBehaviour
{
    private Rigidbody rb; // 缓存引用
void Awake()
{
    rb = GetComponentzuojiankuohaophpcnRigidbodyyoujiankuohaophpcn(); // 一次获取,反复使用
}

void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        rb.AddForce(Vector3.up * 5f);
    }
}

}

GetComponent 返回 null?检查对象生命周期和组件存在性

GetComponent() 返回 null 的原因很具体,不是“没连上”,而是:

  • 目标 GameObject 上**根本没有该组件**(比如想取 AudioSource,但没添加这个组件)
  • 脚本执行时机早于目标组件初始化(例如在 Awake() 里取另一个脚本的字段,而那个脚本的 Awake() 还没跑)
  • 跨 Prefab 实例操作时,用了 transform.Find() 找子物体,但名字拼错或层级变了
  • 用了 GetComponentInParent() 却忘了父物体没挂对应组件

调试建议:加一行日志确认对象是否有效:

var target = GameObject.Find("Enemy");
if (target == null)
{
    Debug.LogError("找不到 Enemy 对象");
    return;
}

var enemyAI = target.GetComponent(); if (enemyAI == null) { Debug.LogError("Enemy 对象上没有 EnemyAI 组件"); }

协程不是线程,WaitForSeconds 不是“暂停整个脚本”

StartCoroutine() 启动的是协程,它和主线程共享上下文,不会阻塞其他逻辑。但新手常误解 yield return new WaitForSeconds(2) 的作用:

  • 它只暂停当前协程,不影响 Update() 或其它协程运行
  • 时间基于 Unity 的时间缩放(Time.timeScale),设为 0 时会卡住 —— 需要用 WaitForSecondsRealtime 替代
  • 协程中不能直接访问未初始化的字段(比如在 Start() 之前就启动协程)

典型用法:延迟触发、分帧加载、渐变效果

IEnumerator FadeOut()
{
    for (float t = 1f; t >= 0; t -= 0.05f)
    {
        renderer.material.color = new Color(1, 1, 1, t);
        yield return null; // 等一帧
    }
}

真正难的不是语法,而是理解 Unity 的执行顺序(Awake → OnEnable → Start → Update → LateUpdate → OnDisable → OnDestroy)和对象依赖关系。哪怕只写一个移动脚本,也要想清楚:位置更新该放哪一帧?输入检测该响应哪个事件?组件引用该在哪一刻拿到?漏掉其中一环,脚本就“看起来没反应”。