很多Unity开发者把设计模式当成面试题来背,背完就忘,写代码的时候还是老一套。我这个系列前面十几篇一直在聊面向对象基础、SOLID原则、常用模式在Unity里的落地,今天这篇是设计模式部分的一个收尾,把那些“虽然不在面试题最前面,但在项目里真的能救命”的剩余模式一次性说清楚。适合那些已经能熟练用MonoBehaviour写功能、但项目越写越乱、想着手重构的Unity开发者,也适合准备进阶的中级程序看。文章会用大量Unity场景举例,尽量不扯理论空话。
1. 为什么还需要一篇“其他设计模式”
1.1 前面没讲完的那些模式去哪了
如果你系统翻过GoF那本经典书,会发现设计模式总共二十多种。很多入门的Unity教程只挑其中五六个讲,单例、工厂、观察者、状态、对象池,讲完就说“设计模式学完了”。但实际进到中大型项目里你会发现,光靠那几个模式根本不够用。比如策划要求技能支持“不同职业有不同伤害倍率”,你用if/else堆;要求新增一种敌人时能复用同一套出生流程,你直接复制粘贴;要求UI各面板之间能不互相引用就把数据传过去,你开始头疼。这些场景恰好需要的是另一批设计模式,我不觉得它们是“冷门”,只是平时科普得少。
这一篇我就打算把策略模式、模板方法模式、命令模式、中介者模式、备忘录模式这些真正在Unity里高频落地的模式补上,再给一个综合示例把多个模式串起来。这正好呼应面向对象的终极目标:让代码能够应对变化,而不是每次变化都翻出全部旧代码做手术。
1.2 筛选原则:游戏开发里真正能用上的模式
我见过很多开发者学设计模式最后学懵了,因为书上每个模式都讲得很抽象,跟游戏场景对不上。所以这一篇里我特意做了一次筛选,标准就三条。
第一,看场景是不是高频。像解释器模式,日常业务逻辑里基本用不到,编译器领域才常见,所以不展开。第二,看能否直接解决Unity的痛点。Unity里最常见的问题是MonoBehaviour脚本互相引用、Update里到处new对象产生GC、多个流程写得到处都是,这些痛点对应的模式我会重点讲。第三,看是否能在一次完整实践中串起来。单一模式看起来简单,真正难的是多个模式协同还不显得臃肿,所以我会专门给一个技能系统的综合示例。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 高频实用的行为型模式拆解
2.1 策略模式:把会变的行为独立出去
策略模式解决的是“同一个东西有多种做法,并且做法之间还能随时换”的问题。举个例子,角色受伤之后的伤害结算,普通攻击、暴击、属性克制,计算逻辑完全不同。多数人第一版直接写了switch:
csharp复制public float CalculateDamage(AttackType type, float baseDamage)
{
switch (type)
{
case AttackType.Normal:
return baseDamage;
case AttackType.Critical:
return baseDamage * 2f;
case AttackType.ElementAdvantage:
return baseDamage * 1.5f;
default:
return baseDamage;
}
}
这写法短期没问题,可一旦策划说“这版本加一个火属性克制,下版本再加一个水系抗性”,这个函数就被反复改,改出bug的几率一次比一次高。策略模式的处理方式是定义一个策略接口,每种算法单独一个类。
csharp复制public interface IDamageStrategy
{
float CalculateDamage(float baseDamage);
}
public class NormalDamageStrategy : IDamageStrategy
{
public float CalculateDamage(float baseDamage) => baseDamage;
}
public class CriticalDamageStrategy : IDamageStrategy
{
private float multiplier = 2f;
public float CalculateDamage(float baseDamage) => baseDamage * multiplier;
}
public class ElementAdvantageStrategy : IDamageStrategy
{
public float CalculateDamage(float baseDamage) => baseDamage * 1.5f;
}
然后把计算参数传进来,由外部决定当前角色用哪种策略。角色类只认策略接口,完全不关心具体算法。好处是新增一种伤害类型时,你不需要去动已经稳定的角色代码,只需要新增一个策略类,在装配处换一下引用就行。这就是面向对象里常说的“开闭原则”,对扩展开放,对修改关闭。
我在项目里实际使用时,一般不会让角色绑死一个策略,而是让策略可以动态替换。比如角色吃到“狂暴”Buff之后,伤害策略从普通换成暴击;Buff结束之后,再换回来。这样角色类的代码几乎不变化,变化的只有运行时策略引用,整个代码结构清爽得多。
2.2 模板方法模式:固定流程,留好扩展点
模板方法模式适合那种“流程骨架完全一样,只有部分步骤实现不同”的场景。我经常举的一个例子是敌人出生流程。不同种类的敌人出生时都要经历“出生特效→初始化属性→激活AI→掉落物品处理”这几个阶段,但每个阶段的具体实现不一样。
如果每个敌人脚本都自己写一套出生流程,会出现两个问题。第一是流程顺序容易乱,有的敌人先做属性初始化再播放特效,有的反过来,排查起来特别痛苦。第二是新增一种敌人时,开发者把旧代码复制一份改改参数,一旦出生流程本身要加一个阶段,所有敌人脚本都得跟着改,很容易漏。
模板方法模式把流程骨架放到父类里,把变化的步骤留成抽象方法让子类实现:
csharp复制public abstract class EnemySpawnFlow
{
public void Spawn(Vector3 pos)
{
PlaySpawnE
