一、怎么创建脚本
在Project窗口下,右键Create C#Script 即可创建脚本
创建脚本的注意事项 :
1)类名和文件名必须一致,不然不能挂载(因为反射机制创建对象,会通过文件名去找Type)
2)没有特殊需求 不用管命名空间
3)创建的脚本默认继承MonoBehavior
4)可以放在Assets文件夹下的任何位置(建议同一文件夹管理)
二、关于MonoBehavior的介绍
首先它是一个基类,任何生命周期函数的执行都需要直接或者间接继承这个类,至于生命周期函数是是什么,我们后面讲。所有 Unity 脚本若需要与引擎生命周期事件(如初始化、更新、销毁等)交互,必须直接或间接继承 MonoBehaviour
。这是 Unity 设计的基础规则,只有继承此类的脚本才能被引擎识别为“组件”,从而挂载到 GameObject
上并参与游戏逻辑的驱动。它有以下特点:
①创建的脚本基本都默认继承MonoBehavior 继承了它才能够挂载在GameObject上。因为Unity 通过继承 MonoBehavior 的类来标记其为“可挂载组件”。Unity 编辑器会扫描脚本是否继承自 MonoBehavior ,只有符合条件的脚本才会出现在“Add Component”菜单中。
②继承了MonoBehavior的脚本不能new 只能挂!!!!!!!!继承 MonoBehaviour
的脚本 不能手动实例化(如 MyScript script = new MyScript();
)。因为Unity 要求组件必须依附于 GameObject
,而 new
创建的实例未绑定任何对象,无法被引擎管理。生命周期函数(如 Awake
、Update
)由引擎自动触发,未挂载的脚本不会执行这些逻辑。
③继承了MonoBehavior的脚本不要去写构造函数,因为我们不会去new它,写构造函数没有任何意义。Unity 不会调用 MonoBehaviour
的构造函数,初始化逻辑应由 Awake
或 Start
实现。构造函数可能在非预期时机执行(如编辑器加载时),导致不可控行为。等后面学习了生命周期函数,就知道一般是使用Awake和Start进行初始化了。
④继承了MonoBehavior的脚本可以在一个对象上挂多个(如果没有加DisallowMultipleComponent特性)例如,为敌人挂载多个“攻击效果”脚本,每个脚本控制不同的攻击逻辑。或为 UI 元素挂载多个“动画控制器”脚本,分别处理不同状态的动画。
⑤继承MonoBehavior的类也可以再次被继承,遵循面向对象继承多态的规则。MonoBehaviour
的子类可以进一步被继承,遵循面向对象的基本原则。子类可以重写父类的生命周期方法(如 Update
),并通过 base.Method()
调用父类逻辑。当然这些都是后话。
了解小知识:为什么会这样设计呢?
组件化开发
每个脚本代表一个独立功能模块,通过组合不同组件构建复杂对象(如角色 = 移动组件 + 动画组件 + 攻击组件)。
避免“上帝脚本”(一个脚本处理所有逻辑),提升代码可维护性。
引擎控制权
Unity 引擎完全掌控组件的生命周期(如创建、更新、销毁),开发者只需填充逻辑,无需手动管理内存或执行顺序。
事件驱动模型
通过生命周期方法和事件回调(如
OnCollisionEnter
)响应游戏状态变化,而非传统的主循环控制。
三、关于不继承Mono的类
①不继承MonoBehavior的类 不能挂载在GameObject上。因为上面提到了,Unity 编辑器通过反射检查脚本是否继承 MonoBehaviour
,只有符合条件的类才会出现在“Add Component”菜单中,只有继承了的才有组件成为权!
②不继承MonoBehavior的类 想怎么写就怎么写 如果要使用需要自己new。非 MonoBehaviour
类本质是普通的 C# 类,不依赖 Unity 的组件系统,因此:
-
无需遵循 Unity 的约束:可以随意定义构造函数、静态方法或字段。
-
实例化方式:必须通过
new
关键字手动创建对象(如MyClass obj = new MyClass();
)。
③不继承MonoBehavior的类一般是单例模式的类(用于管理模块,全局管理游戏状态、资源、配置等。)或者数据结构类(用于存储数据,纯粹用于存储和操作数据。)
④不继承MonoBehavior的类 不用保留默认出现的那几个函数。因为Unity 的生命周期方法(如 Awake
、Update
)是 MonoBehaviour
提供的特性。非 MonoBehaviour
类不具备这些功能,因此:无需保留默认函数:如 Start
或 Update
不会被 Unity 调用,定义它们无意义。
四、关于脚本的执行先后顺序
五、默认脚本内容
你有没有每次发现我们创建的脚本都长得一样,这是因为Untiy自己配置了模版,当然,这模版它Untiy配的,我们就配不得吗,走,我们去看看在哪里配置。
首先:先得来到这个Untiy hub中,点击这个在资源管理器中查看
然后走这个路径:就可以看到很多很多的txt文件,这些就是模版文件
不建议自己随意修改
来来来,做几道题目练练手!!
问题一:MonoBehaviour 的构造函数问题
在 Unity 中,为什么继承 MonoBehaviour
的脚本不应定义构造函数?如果在构造函数中初始化变量,可能会导致什么问题?
参考答案:Unity 不会调用 MonoBehaviour
的构造函数,初始化逻辑应由 Awake
或 Start
完成。构造函数可能在编辑器模式下意外触发(如脚本重新编译时),导致不可控行为。
可能存在的问题:若在构造函数中初始化组件引用(如 GetComponent<T>()
),此时 GameObject
可能未完全初始化,导致空引用异常。构造函数中的逻辑可能破坏 Unity 的组件依赖链,引发初始化顺序混乱。
问题二:单例模式的选择
在 Unity 中,实现单例模式时,为何通常选择不继承 MonoBehaviour
的普通类,而不是通过挂载到 GameObject
的 MonoBehaviour
脚本实现?请比较两种方式的优缺点。
参考答案:
普通类单例(非 MonoBehaviour):
-
优点:
无需依赖
GameObject
,内存占用更低。生命周期完全由代码控制,跨场景时更稳定。
-
缺点:
无法直接使用 Unity 的生命周期方法(如
Update
)。需要手动管理初始化和销毁逻辑
MonoBehaviour 单例:
-
优点:
可方便使用 Unity 生命周期方法(如
Awake
初始化)。可在 Inspector 中配置公开变量。
-
缺点:
依赖
GameObject
,跨场景时需通过DontDestroyOnLoad
防止销毁。可能因场景切换或对象失活导致单例失效。
结论:若单例需要与 Unity 生命周期或场景对象交互(如 UI 管理),使用 MonoBehaviour
;若仅需全局逻辑管理(如配置读取),优先使用普通类单例。
问题三:数据类的设计选择
假设需要设计一个 PlayerData
类,用于存储玩家的血量、等级和装备信息。为什么这个类不应继承 MonoBehaviour
?如果需要在 Inspector 中编辑该类的数据,如何实现?
参考答案:
不继承 MonoBehaviour 的原因:
PlayerData
是纯粹的数据容器,无需与GameObject
绑定或参与 Unity 生命周期。继承
MonoBehaviour
会引入不必要的开销(如组件内存占用),破坏代码简洁性。
在 Inspector 中编辑数据:
将 PlayerData
声明为可序列化的普通类:
[System.Serializable]
public class PlayerData {
public int health;
public int level;
public List<string> equipment;
}
在某个 MonoBehaviour
脚本中声明 PlayerData
的字段,即可在 Inspector 中编辑:
public class Player : MonoBehaviour {
public PlayerData data;
}
这样子就可以了