外观模式(Facade Pattern,也叫门面模式)。如果说之前的“建造者模式”是为了解决单个复杂对象的组装问题,那么“外观模式”则是为了解决一堆复杂子系统的混乱调用问题。

在 Unity 独立开发或中大型项目重构时,外观模式是帮你维持代码整洁、实现“一键轰炸”的绝对拯救者。

💡 为什么要用外观模式?

想象一下,当玩家在主菜单点击“开始游戏”时,你的后台需要发生多少事情:

  1. SaveSystem(存档系统):读取玩家的上一次存档数据。
  2. SceneLoader(场景加载器):异步加载游戏主关卡。
  3. AudioManager(音频管理器):淡出主菜单 BGM,播放关卡背景音乐。
  4. UIManager(UI 系统):关闭主菜单,显示加载条,加载完成后显示玩家 HUD(血条等)。
  5. NetworkManager(网络系统):向服务器同步玩家上线状态。

新手的灾难写法:
在主菜单的“开始按钮”脚本里,把这 5 个系统的引用全部拖进来,然后在一行按钮点击事件里挨个调用它们的方法。
这会导致主菜单脚本和这 5 个系统死死地捆绑在一起。以后如果网络系统要换、或者增加一个“成就系统”,你都得去改主菜单按钮的代码。

外观模式的优雅解法:
给这堆复杂的子系统套上一个“外壳”或者设一个“前台接待员”(Facade)。主菜单只需要对接待员说一句:“我要开始游戏!”接待员自己转身去调度那 5 个复杂的系统,主菜单根本不需要知道后面发生了什么。


一、 Unity 场景下的外观模式代码实现

我们把上面提到的复杂子系统封装到一个叫 GameFlowFacade 的外观类中。

1. 杂乱无章的各个子系统(底层细节)

using UnityEngine;

public class SaveSystem : MonoBehaviour
{
    public void LoadUserData() { Debug.Log("存档系统:已成功读取玩家进度。"); }
}

public class AudioManager : MonoBehaviour
{
    public void SwitchToGameplayMusic() { Debug.Log("音频系统:已切换至战斗 BGM。"); }
}

public class UIManager : MonoBehaviour
{
    public void ShowGameplayHUD() { Debug.Log("UI系统:隐藏主菜单,显示游戏内血条和UI。"); }
}

public class SceneLoader : MonoBehaviour
{
    public void LoadLevel(string levelName) { Debug.Log($"场景系统:正在异步加载【{levelName}】..."); }
}

2. 核心灵魂:外观类 (GameFlowFacade)

外观类把所有的子系统组合在一起,并向外暴露几个极其简单的“一键式”方法。

using UnityEngine;

public class GameFlowFacade : MonoBehaviour
{
    // 内部持有所有子系统的引用(可以在 Inspector 里拖入,也可以用单例)
    [Header("Subsystems")]
    public SaveSystem saveSystem;
    public AudioManager audioManager;
    public UIManager uiManager;
    public SceneLoader sceneLoader;

    // 外观模式提供的核心高层接口:一键启动游戏
    public void EnterGameplay(string targetLevel)
    {
        Debug.Log("==== 外观模式:开始协调各个子系统进行场景切换 ====");
        
        // 按正确的业务逻辑顺序,一条龙服务
        saveSystem.LoadUserData();
        audioManager.SwitchToGameplayMusic();
        uiManager.ShowGameplayHUD();
        sceneLoader.LoadLevel(targetLevel);
        
        Debug.Log("==== 外观模式:所有系统就绪 ====");
    }

    // 外观模式提供的另一个接口:一键退出游戏
    public void ExitGame()
    {
        Debug.Log("外观模式:正在保存数据并安全退出...");
        // 可以在这里调用存档系统的保存、网络系统的断开等
    }
}

3. 客户端调用(主菜单按钮)

现在,主菜单的按钮脚本变得异常干净,它只需要知道 GameFlowFacade 的存在。

using UnityEngine;

public class MainMenuController : MonoBehaviour
{
    // 只需要引入外观类即可,不需要引入 4、5 个管理器
    public GameFlowFacade gameFacade; 

    // 绑定到主菜单“开始游戏”按钮的点击事件上
    public void OnStartButtonClick()
    {
        // 客户端只需要发出一个简单的指令,底层细节全被屏蔽了
        gameFacade.EnterGameplay("World_1_Level_1");
    }
}


二、 外观模式在 Unity 中的王牌拍档:结合单例

细心的你可能已经发现了:在 Unity 中,为了能让任何地方都方便地调用这个“前台接待员”,我们经常会把外观模式(Facade)和单例模式(Singleton)结合在一起

比如,你写了一个 GameManager,它肚子里装着 LevelManagerInputManagerBuffManager
这个 GameManager 本身是一个单例(方便全局访问 GameManager.Instance),但它对外的职责是一个外观(封装了游戏暂停、游戏胜利、游戏失败等一系列涉及多系统的复杂操作)。

public class GameManager : Singleton<GameManager> // 这是一个单例
{
    // 它同时也是一个外观,底下管着一堆复杂的系统
    public CombatSystem combatSystem;
    public ScoreSystem scoreSystem;
    public AchievementSystem achievementSystem;

    // 玩家死亡时,一键触发结算外观
    public void OnPlayerDied()
    {
        combatSystem.DisableAllEnemies();  // 敌人停止攻击
        scoreSystem.CalculateFinalScore();  // 结算分数
        achievementSystem.CheckDeathCount(); // 检查是否达成“屡败屡战”成就
        // ...
    }
}


💡 总结与避坑指南

  • 核心思想: 通过创造一个高层接口,把一堆散乱的子系统(Subsystems)包起来,给上层调用者提供一个简单、统一的“全面屏”界面。

  • 适用场景:

  • 游戏初始化 / 游戏启动引导(Bootstrapping)。

  • 关卡切换、过渡结算(胜利、失败、死亡)。

  • 当你引入了庞大的第三方插件(比如某个复杂的网络 SDK 或物理引擎),你不想让它污染你的核心代码时,写一个 Facade 把它隔离起来。

  • 🚨 避坑指南 —— 不要让 Facade 变成“万能的上帝类”:
    外观模式的职责是“路由和转发”(像前台接待员一样,指引工作给对应部门),它自己不应该包含具体的业务逻辑代码。如果你发现你的 GameFlowFacade 里面开始写如何计算伤害、如何移动物体的具体代码时,说明你走偏了,赶紧把这些逻辑放回到对应的子系统里去。