4、C#脚本基础:脚本创建、MonoBehaviour、Start与Update、变量与类型、方法定义、控制台调试
好,咱们正式开始写代码了。
说实话,很多新手学Unity,一上来就被各种组件、面板搞晕了。但我觉得,真正让你和引擎「对话」的,还是C#脚本。你想想看,没有脚本的游戏,就像一具没有灵魂的躯壳——模型再漂亮,它也不会动。
这一章,我们就从零开始,把C#脚本的底子打扎实。我会把我这些年踩过的坑、养成的习惯,都揉碎了讲给你听。
4.1 脚本的创建与挂载
在Unity里创建脚本,最简单的方式就是在Project窗口右键 → Create → C# Script。我个人习惯直接给脚本起好名字,比如 PlayerController,而不是先建一个「NewBehaviourScript」再改。为什么?因为脚本的文件名必须和类名一致,你后面再改容易出幺蛾子。
创建好脚本后,拖到Hierarchy里的某个游戏对象上,或者选中对象后在Inspector面板点「Add Component」搜索脚本名。挂上去之后,脚本就活了。
嗯,这里要提一句:脚本本身只是一个文本文件,只有挂到游戏对象上,它才会在游戏运行时执行。
4.2 MonoBehaviour:脚本的基类
你打开任何一个新建的脚本,都会看到这么一行:
public class PlayerController : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
这个 MonoBehaviour 是啥?说白了,它就是Unity给咱们准备好的「脚本底座」。只要你的脚本继承自它,就能使用Unity的生命周期函数(比如Start、Update),也能访问组件、协程等核心功能。
我在项目中遇到过不少新手,自己写了一个普通C#类,没继承MonoBehaviour,然后问我为什么挂不到对象上。记住:只有继承MonoBehaviour的类,才能作为组件挂载。
- 所有挂载到游戏对象上的脚本,都必须继承MonoBehaviour
- MonoBehaviour提供了Start、Update、Awake、OnEnable等生命周期方法
- 不要手动new MonoBehaviour的子类,Unity会帮你管理实例
4.3 Start与Update:生命周期入门
这两个方法,是咱们最常用的。我简单说一下它们的区别:
| 方法 | 调用时机 | 用途 |
|---|---|---|
| Start() | 对象被激活时,且只调用一次 | 初始化数据、获取组件引用、设置初始状态 |
| Update() | 每帧调用一次 | 处理输入、移动物体、检测碰撞等持续逻辑 |
你想想看,如果每帧都去获取一次组件引用,那性能得多差?所以获取组件这种操作,放在Start里做一次就够了。而移动、旋转这种需要持续更新的逻辑,就放在Update里。
我曾经犯过一个错误:在Update里用 GetComponent<Rigidbody>() 获取刚体,结果每帧都在做重复查找,帧率直接掉了一半。后来改成在Start里缓存引用,问题就解决了。
4.4 变量与类型:存储数据的基础
变量就是用来存数据的「盒子」。C#是强类型语言,每个变量都得声明它存什么类型的数据。
咱们在Unity里最常用的几种类型:
- int:整数,比如生命值、分数
- float:浮点数,比如速度、位置坐标
- bool:布尔值,true或false,比如是否死亡、是否开门
- string:字符串,比如玩家名字、对话文本
- Vector3:三维向量,表示位置、方向
- GameObject:游戏对象引用
- Transform:位置/旋转/缩放组件
举个例子:
public class PlayerController : MonoBehaviour
{
// 公开变量,可以在Inspector面板里直接修改
public float moveSpeed = 5f;
public int maxHealth = 100;
public string playerName = "Hero";
// 私有变量,只在脚本内部使用
private int currentHealth;
private Vector3 startPosition;
void Start()
{
currentHealth = maxHealth;
startPosition = transform.position;
}
}
这里有个细节:public 变量会在Inspector面板显示出来,方便你调整数值。而 private 变量则隐藏起来,防止不小心改乱。我个人习惯把所有需要外部调整的变量都设为public,内部逻辑用的变量都设为private。
float 类型的变量误写成 int,结果移动速度总是被截断成整数,角色走起来一顿一顿的。后来排查了半天才发现是类型问题。所以定义变量时,一定要想清楚它到底需要小数还是整数。
4.5 方法定义:让脚本动起来
方法就是一段可以重复调用的代码块。咱们把逻辑封装到方法里,代码会清晰很多。
定义一个方法的基本语法:
// 访问修饰符 + 返回类型 + 方法名(参数列表)
public void MoveCharacter(float speed)
{
// 方法体
transform.Translate(Vector3.forward * speed * Time.deltaTime);
}
private int CalculateDamage(int baseDamage, float multiplier)
{
return Mathf.RoundToInt(baseDamage * multiplier);
}
注意看:
void表示这个方法不返回任何值- 如果有返回值,要写具体类型,比如
int、float,并且方法体内要用return返回 - 方法名建议用动词开头,比如
MoveCharacter、TakeDamage,一看就知道是干嘛的
我在项目中遇到过一种情况:有人把所有逻辑都塞在Update里,一个方法写了上千行。后来想加个跳跃功能,根本找不到从哪里下手。所以我的建议是:每个方法只做一件事。比如移动写一个方法,攻击写一个方法,受伤写一个方法。这样后期维护起来,你看着也舒服。
4.6 控制台调试:Debug.Log是你的眼睛
写代码哪有不出错的?关键是出错后怎么快速找到问题。Unity的控制台(Console)就是咱们的「照妖镜」。
最常用的调试方法:
void Start()
{
Debug.Log("游戏开始了!");
Debug.LogWarning("注意:生命值较低");
Debug.LogError("错误:找不到目标对象");
}
这三种输出分别对应普通信息、警告(黄色)、错误(红色)。在控制台里一目了然。
我个人的调试习惯是:
- 在关键逻辑入口处加
Debug.Log,确认代码是否执行到了这里 - 在变量变化时输出当前值,比如
Debug.Log("当前速度:" + currentSpeed); - 用
Debug.LogError标记那些「绝对不应该发生」的情况
Debug.Log 里传入游戏对象,比如 Debug.Log("玩家位置", gameObject);。这样在控制台点击这条日志时,Unity会自动高亮对应的游戏对象,非常方便。
嗯,这里还要说一句:发布游戏时,记得把所有的 Debug.Log 删掉或者注释掉。不然玩家打开控制台,看到满屏的调试信息,体验会很差。我一般会用条件编译 #if UNITY_EDITOR 来包裹调试代码,只在编辑器里输出。
4.7 本章小结
这一章咱们把C#脚本的基础过了一遍。从创建脚本、挂载对象,到理解MonoBehaviour的生命周期,再到变量、方法、调试——这些都是你以后写游戏逻辑的「砖瓦」。
说白了,脚本就是你和Unity沟通的语言。你写得越规范,引擎就越听话。下一章我们会开始写真正的游戏逻辑,比如控制角色移动、跳跃。到时候你会发现,今天学的这些基础,全都会用上。
好,先练练手,打开Unity,创建一个脚本,试着在Start里输出一句话,在Update里让一个方块旋转起来。遇到问题别怕,控制台会告诉你怎么回事。