Appearance
基础概念
Unity 的脚本系统,你可以先理解成:
脚本就是你写的 C# 组件,用来控制 GameObject 的行为。
Unity 里不是“写一个 main 函数,然后程序从 main 开始跑”。 Unity 的逻辑是:你把脚本挂到某个 GameObject 上,Unity 在合适的时机自动调用脚本里的函数。
1. 脚本本质上也是 Component
比如你写一个脚本:
c
using UnityEngine;
public class PlayerController : MonoBehaviour
{
void Update()
{
Debug.Log("玩家正在运行逻辑");
}
}这个 PlayerController 继承了 MonoBehaviour,所以它就可以挂到 GameObject 上。
挂上去后,它就变成这样:
c
Player
├─ Transform
├─ SpriteRenderer
├─ Rigidbody2D
├─ Collider2D
└─ PlayerController也就是说: 脚本不是独立运行的代码文件,而是一个挂在对象身上的行为组件。
2. MonoBehaviour 是 Unity 脚本的核心基类
大多数 Unity 脚本都会这样写:
c
public class MyScript : MonoBehaviour
{
}MonoBehaviour 给了你很多 Unity 特有能力,比如:
- 可以挂到 GameObject 上
- 可以使用
Start() - 可以使用
Update() - 可以访问
transform - 可以使用
GetComponent - 可以启用/禁用脚本
- 可以接收碰撞、触发、鼠标、生命周期事件
如果一个类没有继承 MonoBehaviour,它通常就不能直接挂到 GameObject 上。
3. 脚本生命周期:Unity 自动调用这些函数
这是脚本系统最重要的部分。
常用生命周期顺序大概是:
c
Awake()
↓
OnEnable()
↓
Start()
↓
Update()
↓
LateUpdate()
↓
OnDisable()
↓
OnDestroy()再加一个物理专用的:
c
FixedUpdate()你可以这样理解:
| 函数 | 什么时候执行 | 常见用途 |
|---|---|---|
Awake() | 对象加载时最早执行 | 初始化内部变量、获取组件 |
OnEnable() | 脚本/对象启用时执行 | 监听事件、重置状态 |
Start() | 第一次 Update 前执行一次 | 依赖其他对象初始化后的逻辑 |
Update() | 每帧执行一次 | 输入、普通逻辑、计时 |
FixedUpdate() | 固定时间间隔执行 | 物理移动、Rigidbody 操作 |
LateUpdate() | Update 后执行 | 摄像机跟随、收尾逻辑 |
OnDisable() | 脚本/对象禁用时执行 | 取消监听、暂停逻辑 |
OnDestroy() | 对象销毁时执行 | 清理资源 |
最常见的新手组合是:
c
void Awake()
{
// 获取组件
}
void Start()
{
// 初始化游戏开始状态
}
void Update()
{
// 每帧检查输入
}
void FixedUpdate()
{
// 处理物理移动
}4. Update 和 FixedUpdate 的区别
这个特别重要。
Update() 是每一帧执行一次。 如果电脑性能高,一秒可能执行 120 次;性能低,可能一秒执行 30 次。
适合放:
- 键盘输入
- 鼠标输入
- UI 检查
- 普通逻辑
- 动画状态判断
- 倒计时
例如:
c
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("按下空格");
}
}FixedUpdate() 是固定时间执行,默认大约每秒 50 次。 适合放物理相关逻辑。
比如:
c
void FixedUpdate()
{
rb.linearVelocity = new Vector2(moveX * speed, rb.linearVelocity.y);
}简单记:
输入放 Update,物理放 FixedUpdate。
5. transform 是最常用的内置引用
只要脚本挂在某个 GameObject 上,你就可以直接用:
c
transform它代表当前对象的 Transform 组件。
例如:
c
void Update()
{
transform.position += new Vector3(1, 0, 0) * Time.deltaTime;
}这表示:当前对象每秒往右移动一点。
注意这里用了:
c
Time.deltaTime它表示上一帧到这一帧经过了多少秒。 移动、计时、平滑变化时经常要用它,否则不同帧率下速度会不一样。
6. Inspector 参数:让脚本变得可调
比如:
c
public float speed = 5f;这个字段会出现在 Inspector 面板里。
更推荐这样写:
c
[SerializeField] private float speed = 5f;意思是: 代码里保持 private,但 Unity Inspector 里仍然能调。
完整例子:
c
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField] private float speed = 5f;
void Update()
{
float x = Input.GetAxisRaw("Horizontal");
transform.position += new Vector3(x, 0, 0) * speed * Time.deltaTime;
}
}这样你不用改代码,就可以在 Unity 里调整玩家速度。
7. 脚本怎么访问组件
假设 Player 身上有 Rigidbody2D,脚本也挂在 Player 上。
c
Player
├─ Rigidbody2D
└─ PlayerController脚本里可以这样拿到它:
c
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private Rigidbody2D rb;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
}核心是:
c
GetComponent<Rigidbody2D>();意思是: 从当前 GameObject 身上找到 Rigidbody2D 组件。
常用方式:
c
GetComponent<T>() // 找自己身上的组件
GetComponentInChildren<T>() // 找子对象里的组件
GetComponentInParent<T>() // 找父对象里的组件新手更推荐 Inspector 拖拽引用:
c
[SerializeField] private Rigidbody2D rb;然后在 Inspector 里把对象拖进去。
8. 一个完整的玩家移动脚本
2D 示例:
c
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField] private float speed = 5f;
private Rigidbody2D rb;
private float moveX;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
moveX = Input.GetAxisRaw("Horizontal");
}
void FixedUpdate()
{
rb.linearVelocity = new Vector2(moveX * speed, rb.linearVelocity.y);
}
}这里发生了什么:
c
Awake 获取 Rigidbody2D
Update 读取键盘输入
FixedUpdate 控制物理速度这就是比较标准的脚本分工。
9. 碰撞和触发事件
如果对象身上有 Collider,Unity 可以自动调用碰撞函数。
3D 常用:
c
void OnCollisionEnter(Collision collision)
{
Debug.Log("撞到了:" + collision.gameObject.name);
}2D 常用:
c
void OnCollisionEnter2D(Collision2D collision)
{
Debug.Log("撞到了:" + collision.gameObject.name);
}如果 Collider 勾选了 Is Trigger,用触发函数:
c
void OnTriggerEnter2D(Collider2D other)
{
Debug.Log("进入触发区:" + other.gameObject.name);
}简单区别:
| 类型 | 是否会挡住物体 | 常见用途 |
|---|---|---|
Collision | 会真实碰撞 | 地面、墙、箱子 |
Trigger | 不阻挡,只检测进入 | 金币、传送门、检测区 |
10. 实例化和销毁对象
脚本可以动态创建对象,比如生成子弹、敌人、特效。
c
[SerializeField] private GameObject bulletPrefab;
[SerializeField] private Transform firePoint;
void Shoot()
{
Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
}销毁对象:
c
Destroy(gameObject);其中:
c
gameObject代表当前脚本挂着的那个 GameObject。
11. 协程 Coroutine:延迟和分步骤执行
如果你想“等 2 秒后再做某事”,可以用协程。
c
using System.Collections;
using UnityEngine;
public class Example : MonoBehaviour
{
void Start()
{
StartCoroutine(WaitAndPrint());
}
IEnumerator WaitAndPrint()
{
Debug.Log("开始等待");
yield return new WaitForSeconds(2f);
Debug.Log("2 秒后执行");
}
}协程常用于:
- 延迟执行
- 技能冷却
- 波次刷怪
- 渐隐渐现
- 分阶段动画逻辑
12. 脚本之间怎么通信
常见方式有三种:
第一种:直接拖引用。
c
[SerializeField] private Health health;
void Attack()
{
health.TakeDamage(10);
}第二种:GetComponent。
c
Health health = GetComponent<Health>();
health.TakeDamage(10);第三种:事件系统。
适合稍微进阶后学习,比如玩家死亡通知 UI、音效、任务系统。
新手阶段先掌握前两种就够了。
13. 常见脚本分类
做游戏时,脚本通常会分成几类:
| 脚本类型 | 例子 | 负责什么 |
|---|---|---|
| 控制脚本 | PlayerController | 玩家输入、移动、跳跃 |
| 状态脚本 | Health | 血量、死亡 |
| 生成脚本 | EnemySpawner | 刷怪、生成道具 |
| 管理脚本 | GameManager | 游戏流程、胜负 |
| UI 脚本 | UIManager | 血条、分数、按钮 |
| 数据脚本 | ItemData | 道具、技能、配置 |
| 摄像机脚本 | CameraFollow | 跟随玩家 |
一个好习惯是: 一个脚本只负责一类清晰的事情。
14. 新手最容易犯的错误
第一,脚本文件名和类名不一致。
文件叫:
c
PlayerController.cs类名也应该是:
c
public class PlayerController : MonoBehaviour第二,脚本没有挂到 GameObject 上。 只写了代码,但没有拖到对象上,Unity 不会执行。
第三,把物理移动写在 Update 里。 物理对象建议用 Rigidbody,并在 FixedUpdate 中处理。
第四,到处使用 Find()。 GameObject.Find() 能用,但频繁使用会乱、慢、难维护。
第五,一个脚本写太多功能。 比如 PlayerController 里同时写移动、血量、背包、UI、存档,后面会很难改。
15. 建议你的学习顺序
你可以按这个路线练:
- 写一个脚本,让 Cube 每秒向右移动。
- 用
[SerializeField]把速度显示到 Inspector。 - 用键盘控制 Cube 左右移动。
- 用
GetComponent获取Rigidbody2D。 - 用
FixedUpdate控制物理移动。 - 写碰撞检测,碰到金币后销毁金币。
- 用
Instantiate生成子弹。 - 用
Destroy销毁子弹。 - 写一个
Health脚本控制血量。 - 写一个
GameManager管理胜利和失败。
你可以把 Unity 脚本系统记成这句话:
脚本系统 = MonoBehaviour 生命周期 + Inspector 参数 + 组件访问 + 输入/物理/碰撞/生成逻辑。
掌握这几个,你就能开始真正控制游戏世界了。
参考链接
脚本系统