在游戏开发中,随着功能不断增加,脚本之间的关系往往会越来越复杂。角色要通知UI更新血量,UI要触发音效,任务系统又要依赖角色状态。如果所有逻辑都通过直接调用来实现,代码很快会陷入混乱。这也是为什么需要“开发模式”来帮助我们组织结构。
这次我们介绍两种常见且实用的模式:单例模式和事件驱动模式。
单例模式(Singleton)
在Unity项目中,经常需要全局唯一的管理类,例如声音管理器(AudioManager)或配置管理器(ConfigManager)。这类对象在场景中只需要一个实例,且需要在任意地方方便访问。
一个简单的单例写法如下:
publicclassAudioManager : MonoBehaviour {
publicstatic AudioManager Instance {
get;
privateset;
}
voidAwake(){
if (Instance == null) {
Instance = this;
DontDestroyOnLoad(gameObject);
}
else {
Destroy(gameObject);
}
}
publicvoidPlaySound(string name){
// 播放音效逻辑
}
}
这样,在任意脚本中只需 AudioManager.Instance.PlaySound("hit");
即可调用。
优点 :实现简单,全局访问方便。
缺点 :滥用会导致过度依赖,降低模块独立性。
事件驱动模式(Event System)
当多个对象需要同时响应某个变化时,直接调用往往显得笨拙。例如角色掉血,既要更新UI,又要播放音效,还可能触发Boss的行为。如果逐一调用,不仅耦合度高,而且修改困难。
事件驱动模式提供了解耦的方式:对象不再直接调用彼此,而是通过事件广播与监听进行通信。
一个常见实现是使用C#的 Action:
publicclassPlayer : MonoBehaviour {
publicstatic event Action<int> OnHealthChanged;
privateint health = 100;
publicvoidTakeDamage(int damage){
health -= damage;
OnHealthChanged?.Invoke(health);
}
}
UI脚本可以这样订阅:
voidOnEnable(){
Player.OnHealthChanged += UpdateHealthBar;
}
voidOnDisable(){
Player.OnHealthChanged -= UpdateHealthBar;
}
voidUpdateHealthBar(int hp){
// 更新血条UI
}
当角色掉血时,UI、音效、Boss逻辑都能各自订阅并独立响应,而无需互相知道对方的存在。
优点 :解耦,扩展性强。
缺点 :事件关系复杂时调试不便,需要良好命名和管理。
综合应用
可以将单例模式和事件驱动结合起来,创建一个全局的事件中心(EventManager)。EventManager 本身是一个单例,负责管理和广播所有的游戏事件。
实战小案例
假设有这样一个场景:主角掉血 → UI血条自动更新 → 音效触发 → Boss触发特殊状态。
如果不用设计模式来写,需要在 Player 掉血时,分别调用 UI 血条、音效管理器和 Boss 的相应函数,代码耦合度高。
而使用事件驱动,只需要在 Player 掉血时,触发 OnPlayerHealthChanged 事件,UI、音效和 Boss 都可以自动响应,代码更加简洁清晰。
小结
单例 :适合全局唯一的对象,如管理器类。
事件驱动 :适合多人监听的场景,降低耦合。
组合 :单例事件中心在中小型项目中非常常见。
合理使用这些模式,可以让项目在复杂度提升时依然保持清晰。下一篇我们将介绍 ScriptableObject 的数据驱动用法,它能进一步简化配置和数据管理。
