为什么把 Awake(和 Update)声明为 virtual
如果你不写 virtual,仍然可以在子类里写自己的 Awake。但这会用 方法隐藏(new) 的方式覆盖父类版本,而 Unity 仍然分别调用两份 Awake——父类的、子类的。
一旦你的父类 Awake 里有 必须执行的初始化(拿 CharacterBase 来说:缓存 Animator 和 CharacterController),就需要确认子类永远不会忘记这一步。把它声明为 virtual + 要求子类 override 并手动 base.Awake(),可以在编译期强制提醒。
protected override void Awake()
{
base.Awake(); // 保证父类初始化
/* 子类自己的初始化 */
}
如果不用 virtual 而让子类写 new void Awake(),你稍不注意就会漏掉 base.Awake()——编译器不会给任何警告,运行时才发现 controller = null 之类的问题。
属性的简单写法
private int _health;
public int Health
{
get { return _health; }
set { _health = value; }
}
写法类型 | 示例 | 说明 |
---|---|---|
自动属性 | public int HP { get; set; } | 最常用,编译器自动处理字段 |
只读属性 | public int Level { get; } | 只能在构造器赋值 |
表达式属性 | public float Speed => 10f; | 适合只读并简单返回值 |
私有设置 | public int Score { get; private set; } | 外部只读,内部可写 |
手动属性 | 见完整写法 | 可在 get/set 中写逻辑 |
全局唯一单例(Mono Singleton)模板
泛型单例基类,用于保证某个组件在整个游戏生命周期中只存在一个实例,并且自动持久化到场景之间。
使用类单例
public class XXManager : MonoSingleton<AudioManager>
{
public void PlaySound(string name) { ... }
}
然后你可以在任何地方使用:
XXManager.Instance.PlaySound("click");
where关键字
where
用于规定泛型类型参数 T
必须具备某些特性,如继承某个类、实现某接口、有无参构造函数等。
where T : class
public class MyClass<T> where T : class
限制 T 必须是引用类型(比如 string
、GameObject
、自定义类等)。
where T : struct
限制 T 必须是值类型(如 int, float, Vector3,不能为 Nullable)
where T : new()
public class MyClass<T> where T : new()
限制 T 必须有一个公共的无参构造函数,这样你可以 new T()
。
where T : SomeBaseClass
public class MyClass<T> where T : Enemy
限制 T 必须是 Enemy
类或其子类。这样你可以使用 Enemy
中的方法。
where T : IMyInterface
public class MyClass<T> where T : IAttackable
限制 T 必须实现某个接口,如 IAttackable
多个约束组合使用
public class MyClass<T> where T : MonoBehaviour, new()
限制 T 必须继承自 MonoBehaviour
并且有无参构造函数
单例模板类的递归泛型约束(CRTP,Curiously Recurring Template Pattern)
保证从 MonoSingleton<T>
中返回的 Instance
是派生类类型,不是基类
I
当你希望一个类型能够被比较(用于排序、查找等),你可以让它实现这个接口
namespace System
{
public interface IComparable<in T>
{
int CompareTo(T other);
}
}