0x06. 外观模式
外观模式
在Unity中使用C#实现外观模式(Facade Pattern),核心是为复杂的子系统集群提供一个统一的“门面”接口,简化客户端调用。结合游戏开发典型场景——游戏开局流程(需依次调用资源加载、角色初始化、音效播放、UI显示等多个子系统),能直观体现外观模式“隐藏复杂细节、简化调用”的核心特性。
一、核心思路(外观模式)
| 角色 | 对应实现类 | 作用 |
|---|---|---|
| 子系统类 | ResourceSystem/CharacterSystem/AudioSystem/UISystem |
复杂功能的具体实现者,各自负责独立的子功能(加载资源、创建角色等) |
| 外观类 | GameStartFacade(游戏开局外观类) |
封装所有子系统的调用逻辑,对外提供统一的简化接口(如StartGame()) |
| 客户端 | GameManager(挂载到Unity物体) |
仅调用外观类的接口,无需直接操作子系统 |
二、完整代码实现
1. 子系统类(复杂功能的具体实现)
1.1 资源加载子系统:ResourceSystem
using UnityEngine;
/// <summary>
/// 子系统1:资源加载(场景、预制体、配置表等)
/// </summary>
public class ResourceSystem
{
/// <summary>
/// 加载游戏核心资源(模拟异步加载)
/// </summary>
public void LoadCoreResources()
{
Debug.Log("[资源子系统] 开始加载场景资源...");
// Unity实际开发中可调用Resources.Load/Addressables.LoadAsync
// 模拟加载延迟
Debug.Log("[资源子系统] 角色预制体加载完成!");
Debug.Log("[资源子系统] 关卡配置表加载完成!");
Debug.Log("[资源子系统] 所有核心资源加载完毕✅");
}
/// <summary>
/// 释放无用资源(子系统内部方法,客户端无需关注)
/// </summary>
private void UnloadUnusedResources()
{
Resources.UnloadUnusedAssets();
Debug.Log("[资源子系统] 已释放无用资源");
}
}
1.2 角色初始化子系统:CharacterSystem
/// <summary>
/// 子系统2:角色初始化(创建玩家、加载存档、初始化属性)
/// </summary>
public class CharacterSystem
{
/// <summary>
/// 初始化玩家角色
/// </summary>
/// <param name="playerName">玩家名称</param>
public void InitPlayer(string playerName)
{
Debug.Log($"[角色子系统] 开始初始化玩家:{playerName}");
// 模拟加载玩家存档、初始化属性
Debug.Log($"[角色子系统] 玩家{playerName}存档加载完成!");
Debug.Log($"[角色子系统] 玩家{playerName}属性初始化完成(HP=1000,攻击=200)✅");
}
}
1.3 音效播放子系统:AudioSystem
/// <summary>
/// 子系统3:音效管理(背景音乐、音效播放)
/// </summary>
public class AudioSystem
{
/// <summary>
/// 播放开局背景音乐
/// </summary>
public void PlayBGM()
{
Debug.Log("[音效子系统] 开始播放开局背景音乐《勇者出征》✅");
// Unity实际开发中可调用AudioSource.Play()
}
/// <summary>
/// 停止所有音效(子系统内部方法)
/// </summary>
private void StopAllAudio()
{
Debug.Log("[音效子系统] 已停止所有音效");
}
}
1.4 UI显示子系统:UISystem
/// <summary>
/// 子系统4:UI管理(显示开局界面、隐藏加载界面)
/// </summary>
public class UISystem
{
/// <summary>
/// 显示游戏主界面
/// </summary>
public void ShowMainUI()
{
Debug.Log("[UI子系统] 隐藏加载界面,显示游戏主界面✅");
// Unity实际开发中可控制Canvas/Panel的显示隐藏
}
/// <summary>
/// 显示加载进度(子系统内部方法)
/// </summary>
/// <param name="progress">进度0-100</param>
private void ShowLoadingProgress(int progress)
{
Debug.Log($"[UI子系统] 加载进度:{progress}%");
}
}
2. 外观类:GameStartFacade(统一门面接口)
/// <summary>
/// 外观类:游戏开局门面,封装所有子系统的复杂调用逻辑
/// </summary>
public class GameStartFacade
{
// 持有所有子系统的引用
private readonly ResourceSystem _resourceSystem;
private readonly CharacterSystem _characterSystem;
private readonly AudioSystem _audioSystem;
private readonly UISystem _uiSystem;
// 初始化所有子系统
public GameStartFacade()
{
_resourceSystem = new ResourceSystem();
_characterSystem = new CharacterSystem();
_audioSystem = new AudioSystem();
_uiSystem = new UISystem();
}
/// <summary>
/// 对外暴露的简化接口:一键启动游戏
/// </summary>
/// <param name="playerName">玩家名称</param>
public void StartGame(string playerName)
{
Debug.Log("========== 开始游戏开局流程 ==========\n");
// 按顺序调用子系统(复杂逻辑封装在外观类内部)
_resourceSystem.LoadCoreResources();
Debug.Log(""); // 空行分隔
_characterSystem.InitPlayer(playerName);
Debug.Log("");
_audioSystem.PlayBGM();
Debug.Log("");
_uiSystem.ShowMainUI();
Debug.Log("\n========== 游戏开局流程完成 ==========");
}
/// <summary>
/// 可选:对外暴露的简化接口——退出游戏
/// </summary>
public void ExitGame()
{
Debug.Log("\n========== 开始退出游戏流程 ==========");
// 调用子系统的清理逻辑(即使子系统方法是private,外观类可通过public方法间接调用)
Debug.Log("[资源子系统] 释放所有资源");
Debug.Log("[音效子系统] 停止背景音乐");
Debug.Log("[UI子系统] 显示退出确认界面");
Debug.Log("========== 退出流程准备完成 ==========");
}
}
3. 客户端调用:GameManager(Unity场景挂载)
using UnityEngine;
/// <summary>
/// 游戏管理器(客户端):仅调用外观类的简化接口,无需关注子系统细节
/// </summary>
public class GameManager : MonoBehaviour
{
private GameStartFacade _gameStartFacade;
private void Awake()
{
// 初始化外观类
_gameStartFacade = new GameStartFacade();
}
private void Start()
{
// 一键启动游戏(仅需调用外观类的一个方法,无需操作任何子系统)
_gameStartFacade.StartGame("勇者一号");
// 模拟5秒后退出游戏(测试退出接口)
Invoke(nameof(ExitGameTest), 5f);
}
private void ExitGameTest()
{
_gameStartFacade.ExitGame();
}
}
三、Unity运行效果
将GameManager挂载到场景任意物体(如MainCamera),运行后控制台输出:
========== 开始游戏开局流程 ==========
[资源子系统] 开始加载场景资源...
[资源子系统] 角色预制体加载完成!
[资源子系统] 关卡配置表加载完成!
[资源子系统] 所有核心资源加载完毕✅
[角色子系统] 开始初始化玩家:勇者一号
[角色子系统] 玩家勇者一号存档加载完成!
[角色子系统] 玩家勇者一号属性初始化完成(HP=1000,攻击=200)✅
[音效子系统] 开始播放开局背景音乐《勇者出征》✅
[UI子系统] 隐藏加载界面,显示游戏主界面✅
========== 游戏开局流程完成 ==========
// 5秒后输出:
========== 开始退出游戏流程 ==========
[资源子系统] 释放所有资源
[音效子系统] 停止背景音乐
[UI子系统] 显示退出确认界面
========== 退出流程准备完成 ==========
四、Unity场景扩展(贴合实际开发)
1. 结合Unity异步加载(Addressables)
实际项目中,资源加载是异步的,可在外观类中封装异步逻辑,客户端只需等待回调:
/// <summary>
/// 外观类中封装异步加载逻辑
/// </summary>
public class GameStartFacade
{
// ... 原有子系统引用
/// <summary>
/// 异步启动游戏(带回调)
/// </summary>
/// <param name="playerName">玩家名称</param>
/// <param name="onComplete">完成回调</param>
public async void StartGameAsync(string playerName, Action onComplete)
{
Debug.Log("========== 异步开始游戏开局流程 ==========\n");
// 异步加载资源(Unity Addressables示例)
// var handle = Addressables.LoadAssetsAsync<GameObject>("Character", null);
// await handle.Task;
Debug.Log("[资源子系统] 异步加载资源完成!");
_characterSystem.InitPlayer(playerName);
_audioSystem.PlayBGM();
_uiSystem.ShowMainUI();
Debug.Log("\n========== 异步开局流程完成 ==========");
onComplete?.Invoke();
}
}
// 客户端调用异步接口
private void Start()
{
_gameStartFacade.StartGameAsync("勇者一号", () =>
{
Debug.Log("【客户端】游戏已成功启动,可开始交互!");
});
}
2. 子系统单例化(Unity常用优化)
将子系统改为单例模式,避免重复创建实例,符合Unity开发习惯:
/// <summary>
/// 单例化的资源子系统
/// </summary>
public class ResourceSystem
{
// 单例实例
private static ResourceSystem _instance;
public static ResourceSystem Instance =>
_instance ?? (_instance = new ResourceSystem());
// 私有构造器,禁止外部new
private ResourceSystem() { }
// ... 原有LoadCoreResources方法
}
// 外观类中使用单例
public class GameStartFacade
{
private readonly ResourceSystem _resourceSystem = ResourceSystem.Instance;
// ... 其他子系统同理
}
3. 动态扩展子系统(符合开闭原则)
新增子系统(如SaveSystem存档子系统)时,仅需修改外观类,客户端调用逻辑完全不变:
/// <summary>
/// 新增子系统:存档管理
/// </summary>
public class SaveSystem
{
public void LoadSaveData(string playerName)
{
Debug.Log($"[存档子系统] 加载玩家{playerName}的存档数据✅");
}
}
// 外观类中集成新子系统
public class GameStartFacade
{
private readonly SaveSystem _saveSystem = new SaveSystem();
public void StartGame(string playerName)
{
Debug.Log("========== 开始游戏开局流程 ==========\n");
_resourceSystem.LoadCoreResources();
Debug.Log("");
_saveSystem.LoadSaveData(playerName); // 新增调用
Debug.Log("");
_characterSystem.InitPlayer(playerName);
// ... 原有其他子系统调用
}
}
// 客户端调用不变,仍只需一行代码
_gameStartFacade.StartGame("勇者一号");
4. 错误处理封装(简化客户端异常处理)
在外观类中统一处理子系统的异常,客户端无需关注具体错误类型:
public void StartGame(string playerName)
{
try
{
_resourceSystem.LoadCoreResources();
_characterSystem.InitPlayer(playerName);
_audioSystem.PlayBGM();
_uiSystem.ShowMainUI();
}
catch (Exception ex)
{
// 统一错误处理(显示错误UI、记录日志)
Debug.LogError($"[外观类] 开局流程出错:{ex.Message}");
_uiSystem.ShowErrorUI("游戏启动失败,请重试!");
}
}
五、外观模式核心优势(Unity场景)
- 简化调用:客户端仅需调用外观类的1-2个方法,即可完成复杂的多子系统联动(比如开局流程从“调用4个子系统的8个方法”简化为“调用1个StartGame方法”);
- 解耦客户端与子系统:客户端完全不依赖子系统,子系统的修改(如资源加载方式从Resources改为Addressables)无需修改客户端代码;
- 统一管理复杂逻辑:多子系统的调用顺序、依赖关系都封装在外观类中,便于维护(比如开局必须先加载资源,再初始化角色);
- 降低学习成本:新开发者无需了解所有子系统的细节,只需调用外观类的接口即可完成核心功能。
六、外观模式 vs 其他模式(Unity场景对比)
| 模式 | 核心差异 | Unity适用场景 |
|---|---|---|
| 外观模式 | 封装多个子系统,提供统一接口 | 复杂流程调用(开局/结算/退出游戏、下单支付流程) |
| 工厂模式 | 关注“创建对象” | 单个对象的创建(角色、道具) |
| 建造者模式 | 关注“组装对象”,分步定制 | 复杂对象的组件组装(角色定制、UI面板组装) |
外观模式是Unity中开发“复杂流程型功能”的首选模式,比如游戏开局、关卡结算、商城下单、玩家退出等需要多系统联动的场景,能极大简化客户端代码,降低维护成本。