一、什么是设计模式

设计模式(Design Pattern)是软件开发中对反复出现的设计问题的可复用解决方案。它不是具体的代码,而是一种"套路"——告诉你类和对象该怎么组织,才能写出可扩展、可维护、低耦合的代码。

它的理论源头是 1994 年"四人帮"(GoF)的《设计模式:可复用面向对象软件的基础》,书中总结了 23 种经典模式,分为三大类:

分类 解决什么问题
创建型(5 种) 对象怎么"new"出来,创建过程与使用解耦
结构型(7 种) 类和对象怎么"拼"在一起,组成更大的结构
行为型(11 种) 对象之间怎么"交互",职责和算法怎么分配

学习设计模式之前,建议先记住背后的核心思想——SOLID 原则(单一职责、开闭原则、里氏替换、接口隔离、依赖倒置)。几乎所有设计模式都是在落实"开闭原则":对扩展开放,对修改关闭。


二、23 种 GoF 模式速览

创建型模式

模式 一句话说明 Unity 场景举例
单例 Singleton 保证全局只有一个实例 GameManager、AudioManager
工厂方法 Factory Method 用方法封装对象的创建 根据类型生成不同敌人
抽象工厂 Abstract Factory 创建"一族"相关对象 不同关卡主题的一整套怪物+场景
建造者 Builder 分步骤构建复杂对象 角色创建器(捏脸系统)
原型 Prototype 通过克隆创建对象 Instantiate(prefab) 本质就是原型

结构型模式

模式 一句话说明 Unity 场景举例
适配器 Adapter 接口不兼容时做转换 接入第三方 SDK 统一接口
桥接 Bridge 抽象与实现分离,各自独立变化 渲染逻辑与平台分离
组合 Composite 树形结构统一对待个体与整体 场景层级、UI 层级、技能树
装饰 Decorator 动态给对象加功能 武器附魔、Buff 叠加
外观 Facade 给复杂子系统一个简单入口 封装存档系统对外只暴露 Save/Load
享元 Flyweight 共享细粒度对象省内存 瓦片地图、大量树木/子弹共享数据
代理 Proxy 控制对对象的访问 资源懒加载、网络代理

行为型模式

模式 一句话说明 Unity 场景举例
责任链 Chain of Responsibility 请求沿链传递直到被处理 事件冒泡、伤害结算流程
命令 Command 把请求封装成对象 撤销/重做、输入录制回放
解释器 Interpreter 定义语法并解释执行 技能脚本、对话 DSL
迭代器 Iterator 顺序访问集合元素 C# 的 foreach / IEnumerator
中介者 Mediator 对象间通过中介通信,避免网状依赖 UI 面板之间通过 UIManager 通信
备忘录 Memento 保存和恢复状态 存档/读档、快照回滚
观察者 Observer 一对多订阅通知 事件系统、成就系统
状态 State 状态改变时行为改变 AI 状态机、角色状态(跑/跳/攻击)
策略 Strategy 算法族可互换 武器攻击方式、寻路算法切换
模板方法 Template Method 骨架固定,步骤留给子类 关卡流程基类
访问者 Visitor 对结构中元素做操作而不改类 对场景中对象批量导出/统计

三、Unity 开发中最常用的模式 + 代码实战

理论说完,下面挑游戏开发中真正高频使用的模式,逐一给出可直接用的 Unity C# 实现。

1. 单例模式(Singleton)—— 使用频率最高

场景:GameManager、AudioManager、UIManager 这类全局唯一的管理者。

最实用的是泛型 MonoBehaviour 单例,任何管理器继承它即可:

using UnityEngine;

public class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
    private static T _instance;

    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<T>();
                if (_instance == null)
                {
                    var go = new GameObject(typeof(T).Name);
                    _instance = go.AddComponent<T>();
                }
            }
            return _instance;
        }
    }

    protected virtual void Awake()
    {
        if (_instance != null && _instance != this)
        {
            Destroy(gameObject);   // 场景中已存在,销毁重复者
            return;
        }
        _instance = (T)this;
        DontDestroyOnLoad(gameObject); // 切场景不销毁
    }
}

使用:

public class AudioManager : MonoSingleton<AudioManager>
{
    public void PlaySFX(AudioClip clip)
    {
        // 播放音效……
    }
}

// 任意地方调用:
AudioManager.Instance.PlaySFX(clip);

警告:单例是最容易被滥用的模式。满项目都是 XxxManager.Instance 会形成隐式全局依赖,难以测试。只给真正的全局服务用。


2. 观察者模式(Observer)—— 解耦的利器

场景:玩家掉血 → UI 更新血条、播放受击音效、触发成就。如果让 Player 直接引用这三个系统,耦合就爆炸了。用事件订阅解决。

C# 原生就支持:event + Action。

using System;
using UnityEngine;

public class PlayerHealth : MonoBehaviour
{
    public event Action<int, int> OnHealthChanged; // 当前血量, 最大血量

    [SerializeField] private int maxHp = 100;
    private int _hp;

    private void Start()
    {
        _hp = maxHp;
        OnHealthChanged?.Invoke(_hp, maxHp);
    }

    public void TakeDamage(int damage)
    {
        _hp = Mathf.Max(0, _hp - damage);
        OnHealthChanged?.Invoke(_hp, maxHp); // 发布事件,谁关心谁订阅
    }
}

订阅方(UI):

public class HealthBarUI : MonoBehaviour
{
    [SerializeField] private PlayerHealth player;
    [SerializeField] private UnityEngine.UI.Slider slider;

    private void OnEnable()  => player.OnHealthChanged += UpdateBar; // 订阅
    private void OnDisable() => player.OnHealthChanged -= UpdateBar; // 必须退订!

    private void UpdateBar(int hp, int max)
    {
        slider.value = (float)hp / max;
    }
}

再进一步,可以做一个全局事件中心(EventBus),让完全不相干的系统也能通信:

using System;
using System.Collections.Generic;

public static class EventCenter
{
    private static readonly Dictionary<string, Delegate> _table = new();

    public static void Subscribe(string eventName, Action callback)
    {
        _table[eventName] = _table.TryGetValue(eventName, out var d)
            ? Delegate.Combine(d, callback)
            : callback;
    }

    public static void Unsubscribe(string eventName, Action callback)
    {
        if (!_table.TryGetValue(eventName, out var d)) return;
        var result = Delegate.Remove(d, callback);
        if (result == null) _table.Remove(eventName);
        else _table[eventName] = result;
    }

    public static void Publish(string eventName)
    {
        if (_table.TryGetValue(eventName, out var d))
            (d as Action)?.Invoke();
    }
}

// 使用:
EventCenter.Subscribe("GameOver", ShowGameOverPanel);
EventCenter.Publish("GameOver");

Unity 特色做法:还可以用 UnityEvent(可在 Inspector 里拖连线)或 ScriptableObject 事件,把事件本身做成资产文件,进一步降低代码依赖。


3. 状态模式(State)—— AI 与角色控制的核心

场景:敌人有"巡逻 → 追击 → 攻击 → 死亡"等状态。如果用一堆 if-else 和 bool 标志位写,状态一多就是灾难。

// 状态接口
public interface IState
{
    void Enter();
    void Tick();   // 每帧执行
    void Exit();
}

// 状态机(纯 C# 类,不继承 MonoBehaviour,可复用)
public class StateMachine
{
    public IState Current { get; private set; }

    public void ChangeState(IState next)
    {
        Current?.Exit();
        Current = next;
        Current.Enter();
    }

    public void Tick() => Current?.Tick();
}

具体状态(以敌人 AI 为例):

public class PatrolState : IState
{
    private readonly Enemy _enemy;
    public PatrolState(Enemy enemy) => _enemy = enemy;

    public void Enter() => _enemy.Animator.Play("Walk");
    public void Tick()
    {
        _enemy.MoveAlongWaypoints();
        if (_enemy.CanSeePlayer)
            _enemy.FSM.ChangeState(new ChaseState(_enemy)); // 切换状态
    }
    public void Exit() { }
}

public class ChaseState : IState
{
    private readonly Enemy _enemy;
    public ChaseState(Enemy enemy) => _enemy = enemy;

    public void Enter() => _enemy.Animator.Play("Run");
    public void Tick()
    {
        _enemy.ChasePlayer();
        if (_enemy.DistanceToPlayer < 2f)
            _enemy.FSM.ChangeState(new AttackState(_enemy));
        else if (!_enemy.CanSeePlayer)
            _enemy.FSM.ChangeState(new PatrolState(_enemy));
    }
    public void Exit() { }
}

驱动者:

public class Enemy : MonoBehaviour
{
    public StateMachine FSM { get; private set; } = new StateMachine();

    private void Start() => FSM.ChangeState(new PatrolState(this));
    private void Update() => FSM.Tick();
}

每加一个状态只是新增一个类,完全不改动旧代码——这就是开闭原则的体现。


4. 对象池(Object Pool)—— 性能优化必备

场景:子弹、特效、敌人尸体频繁 Instantiate/Destroy 会产生 GC 卡顿。对象池"用完归还、循环使用"。

Unity 2021+ 内置了 UnityEngine.Pool:

using UnityEngine;
using UnityEngine.Pool;

public class BulletSpawner : MonoBehaviour
{
    [SerializeField] private Bullet bulletPrefab;
    private ObjectPool<Bullet> _pool;

    private void Awake()
    {
        _pool = new ObjectPool<Bullet>(
            createFunc:       () => Instantiate(bulletPrefab),      // 池空时创建
            actionOnGet:      b  => b.gameObject.SetActive(true),   // 取出时激活
            actionOnRelease:  b  => b.gameObject.SetActive(false),  // 归还时隐藏
            actionOnDestroy:  b  => Destroy(b.gameObject),          // 超出容量销毁
            defaultCapacity: 20,
            maxSize: 100);
    }

    public void Fire(Vector3 pos, Vector3 dir)
    {
        Bullet b = _pool.Get();
        b.transform.position = pos;
        b.Launch(dir, onDead: () => _pool.Release(b)); // 子弹消亡时归还
    }
}

5. 命令模式(Command)—— 撤销/重做、输入系统

场景:RTS 操作队列、棋类游戏悔棋、解谜游戏回退步数。把"一次操作"封装成对象,就能存起来、排队、撤销。

public interface ICommand
{
    void Execute();
    void Undo();
}

public class MoveCommand : ICommand
{
    private readonly Transform _target;
    private readonly Vector3 _from;
    private readonly Vector3 _to;

    public MoveCommand(Transform target, Vector3 to)
    {
        _target = target;
        _from = target.position;
        _to = to;
    }

    public void Execute() => _target.position = _to;
    public void Undo()    => _target.position = _from;
}

public class CommandManager
{
    private readonly Stack<ICommand> _history = new();

    public void Do(ICommand command)
    {
        command.Execute();
        _history.Push(command);
    }

    public void Undo()
    {
        if (_history.Count > 0)
            _history.Pop().Undo();
    }
}

在 MonoBehaviour 里使用:

public class ChessController : MonoBehaviour
{
    private readonly CommandManager _cmdManager = new();

    public void MovePiece(Transform piece, Vector3 targetPos)
        => _cmdManager.Do(new MoveCommand(piece, targetPos));

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Z))
            _cmdManager.Undo(); // 按 Z 悔棋
    }
}

6. 工厂模式(Factory)—— 统一创建逻辑

场景:刷怪系统。调用方只说"我要一只骷髅",不关心它怎么初始化。

public enum EnemyType { Slime, Skeleton, Dragon }

public class EnemyFactory : MonoBehaviour
{
    [SerializeField] private GameObject slimePrefab;
    [SerializeField] private GameObject skeletonPrefab;
    [SerializeField] private GameObject dragonPrefab;

    public Enemy Create(EnemyType type, Vector3 position)
    {
        GameObject prefab = type switch
        {
            EnemyType.Slime    => slimePrefab,
            EnemyType.Skeleton => skeletonPrefab,
            EnemyType.Dragon   => dragonPrefab,
            _ => throw new System.ArgumentOutOfRangeException(nameof(type))
        };

        var go = Instantiate(prefab, position, Quaternion.identity);
        var enemy = go.GetComponent<Enemy>();
        enemy.Init(); // 统一初始化入口(读配置表、绑定事件等)
        return enemy;
    }
}

更进阶的做法是结合 ScriptableObject 存怪物数据,工厂查表生成,新增怪物时零代码改动。


7. 策略模式(Strategy)—— 可插拔的算法

场景:同一个士兵可以拿剑(近战)、拿弓(远程)、用法杖(AOE)。攻击方式是可换的"算法"。

public interface IAttackStrategy
{
    void Attack(Vector3 origin, Vector3 targetPos);
}

public class MeleeAttack : IAttackStrategy
{
    public void Attack(Vector3 origin, Vector3 targetPos)
    {
        // 近战判定:检测 origin 前方扇形范围
        Debug.Log("挥剑近战攻击!");
    }
}

public class RangedAttack : IAttackStrategy
{
    private readonly BulletSpawner _spawner;
    public RangedAttack(BulletSpawner spawner) => _spawner = spawner;

    public void Attack(Vector3 origin, Vector3 targetPos)
    {
        _spawner.Fire(origin, (targetPos - origin).normalized);
        Debug.Log("射箭远程攻击!");
    }
}

使用者运行时自由切换:

public class Soldier : MonoBehaviour
{
    private IAttackStrategy _strategy;
    [SerializeField] private Transform target;

    public void EquipWeapon(IAttackStrategy strategy) => _strategy = strategy;

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
            _strategy?.Attack(transform.position, target.position);
    }
}

新增一种攻击方式 = 新增一个实现 IAttackStrategy 的类,士兵的代码一行不用改。


8. 组件模式(Component)—— Unity 的立身之本

Unity 引擎本身就是组件模式的教科书:

  • GameObject 只是一个容器,本身没有任何行为;
  • 所有能力(渲染、碰撞、移动、音效)都是 Component —— Rigidbody、Collider、AudioSource、你写的脚本……
  • 想给物体加功能 = 组合组件,而不是继承出 MonsterWithFlyableAndShootable 这种继承地狱。

这就是为什么官方反复强调"组合优于继承"。自己写代码时同理:把"血量""移动""背包"拆成独立组件挂到物体上,而不是写一个巨大的 CharacterBase 基类。


四、补充:游戏开发特有的模式

GoF 23 式之外,游戏圈还有一本经典《Game Programming Patterns》(Robert Nystrom),其中和 Unity 强相关的:

  • 游戏循环(Game Loop):Update() + FixedUpdate() 就是;
  • 更新方法(Update Method):每帧逐对象更新;
  • 双缓冲(Double Buffer):Unity 渲染管线的帧缓冲;
  • 事件队列(Event Queue):解耦输入与处理时机;
  • 服务定位器(Service Locator):单例的替代方案,用注册表查找服务,方便替换实现(比如真机/编辑器用不同存档服务)。

五、避坑指南:不要过度设计

  1. 先写能跑的代码,再重构出模式——模式是重构的终点,不是设计的起点。小项目里生搬抽象工厂只会增加复杂度。
  2. 单例别滥用,优先考虑依赖注入或 ScriptableObject 共享数据。
  3. 模式之间常组合使用:状态机里的状态可以用工厂创建,AI 的攻击可以注入策略,UI 用观察者监听数据。
  4. 判断标准很简单:如果加一个新需求(新怪物、新武器、新状态)时,你需要改动的旧代码越少,说明结构越好。

建议学习路径:单例 → 观察者 → 状态 → 对象池 → 策略/工厂 → 命令,这六个吃透了,Unity 项目里 90% 的架构问题都能应对。剩下的模式遇到具体场景时再翻出来查。