外观模式(Facade):为复杂子系统提供 "统一的简化接口",降低调用者复杂度。
外观模式相关具体代码如下所示:
cs
using UnityEngine;
using System;
// 子系统1:音频系统
public class AudioSystem
{
public void PlayBackgroundMusic(string trackName)
{
Debug.Log($"播放背景音乐: {trackName}");
}
public void StopBackgroundMusic()
{
Debug.Log("停止背景音乐");
}
public void PlaySoundEffect(string effectName, Vector3 position)
{
Debug.Log($"在位置 {position} 播放音效: {effectName}");
}
}
// 子系统2:UI系统
public class UISystem
{
public void ShowMainMenu()
{
Debug.Log("显示主菜单");
}
public void HideMainMenu()
{
Debug.Log("隐藏主菜单");
}
public void ShowHUD()
{
Debug.Log("显示游戏HUD界面");
}
public void UpdateScore(int score)
{
Debug.Log($"更新分数显示: {score}");
}
}
// 子系统3:场景管理系统
public class SceneSystem
{
public void LoadGameScene(string sceneName)
{
Debug.Log($"加载游戏场景: {sceneName}");
// 实际项目中会调用SceneManager.LoadScene()
}
public void UnloadCurrentScene()
{
Debug.Log("卸载当前场景");
}
public void RestartCurrentScene()
{
Debug.Log("重启当前场景");
}
}
// 子系统4:存档系统
public class SaveSystem
{
public void SaveGame(string saveName)
{
Debug.Log($"保存游戏到存档: {saveName}");
}
public void LoadGame(string saveName)
{
Debug.Log($"从存档加载游戏: {saveName}");
}
public bool HasSaveData()
{
return true; // 简化示例,始终返回有存档
}
}
// 外观类:提供统一接口封装子系统
public class GameManagerFacade : MonoBehaviour
{
// 子系统实例
private AudioSystem audioSystem;
private UISystem uiSystem;
private SceneSystem sceneSystem;
private SaveSystem saveSystem;
private void Awake()
{
// 初始化子系统
audioSystem = new AudioSystem();
uiSystem = new UISystem();
sceneSystem = new SceneSystem();
saveSystem = new SaveSystem();
}
// 封装的开始新游戏流程
public void StartNewGame()
{
Debug.Log("\n=== 开始新游戏 ===");
uiSystem.HideMainMenu();
sceneSystem.LoadGameScene("Level1");
audioSystem.PlayBackgroundMusic("BattleTheme");
uiSystem.ShowHUD();
uiSystem.UpdateScore(0);
}
// 封装的继续游戏流程
public void ContinueGame()
{
if (saveSystem.HasSaveData())
{
Debug.Log("\n=== 继续游戏 ===");
uiSystem.HideMainMenu();
saveSystem.LoadGame("AutoSave");
audioSystem.PlayBackgroundMusic("BattleTheme");
uiSystem.ShowHUD();
}
else
{
Debug.Log("\n没有可用存档,开始新游戏");
StartNewGame();
}
}
// 封装的游戏结束流程
public void GameOver()
{
Debug.Log("\n=== 游戏结束 ===");
audioSystem.StopBackgroundMusic();
audioSystem.PlaySoundEffect("GameOver", Vector3.zero);
uiSystem.ShowMainMenu();
sceneSystem.UnloadCurrentScene();
}
// 封装的保存游戏流程
public void SaveCurrentGame()
{
Debug.Log("\n=== 保存游戏 ===");
saveSystem.SaveGame("AutoSave");
audioSystem.PlaySoundEffect("SaveSuccess", Vector3.zero);
}
// 封装的更新分数流程
public void AddScore(int points)
{
// 内部可能涉及多个子系统协作
uiSystem.UpdateScore(points);
audioSystem.PlaySoundEffect("ScoreUp", Vector3.zero);
}
}
// 客户端:只与外观类交互
public class GameClient : MonoBehaviour
{
public GameManagerFacade gameManager;
private void Start()
{
if (gameManager == null)
{
gameManager = FindObjectOfType<GameManagerFacade>();
}
// 显示主菜单(初始状态)
// 注意:客户端不需要知道具体哪个子系统处理这个请求
// 模拟用户输入
Invoke("UserStartNewGame", 1f);
Invoke("UserAddScore", 2f);
Invoke("UserSaveGame", 3f);
Invoke("UserGameOver", 4f);
}
private void UserStartNewGame()
{
gameManager.StartNewGame();
}
private void UserAddScore()
{
gameManager.AddScore(150);
}
private void UserSaveGame()
{
gameManager.SaveCurrentGame();
}
private void UserGameOver()
{
gameManager.GameOver();
}
}
外观模式的优势:
简化了客户端的使用,将复杂的子系统交互封装为简单接口;降低了客户端与子系统之间的耦合度,子系统的变化不会影响客户端;隐藏了系统的内部复杂性,使代码更易于维护和扩展;可以有选择地暴露子系统功能,控制访问权限。在 Unity 中,外观模式非常适合实现GameManager
这类核心管理器,将音频、UI、场景、存档等分散的系统整合起来,为其他脚本提供简洁的接口。
享元模式(Flyweight):复用大量 "相同 / 相似属性" 的对象,减少内存消耗
享元模式相关具体代码如下所示:
cs
using UnityEngine;
using System.Collections.Generic;
// 享元对象:存储可共享的精灵数据
public class SpriteFlyweight
{
public Sprite Sprite; // 共享的精灵图
public string SpriteName; // 精灵名称
public Vector2 Pivot; // 精灵锚点
public Vector2 Size; // 精灵尺寸
public SpriteFlyweight(Sprite sprite)
{
Sprite = sprite;
SpriteName = sprite.name;
Pivot = sprite.pivot;
Size = sprite.rect.size;
}
}
// 享元工厂:管理和提供享元对象
public class SpriteFlyweightFactory
{
private Dictionary<string, SpriteFlyweight> flyweights = new Dictionary<string, SpriteFlyweight>();
// 获取或创建享元对象
public SpriteFlyweight GetFlyweight(Sprite sprite)
{
if (sprite == null) return null;
string key = sprite.name;
// 如果已存在则返回共享实例,否则创建新的享元对象
if (!flyweights.ContainsKey(key))
{
flyweights[key] = new SpriteFlyweight(sprite);
Debug.Log($"创建新的享元对象: {key}");
}
else
{
Debug.Log($"复用享元对象: {key}");
}
return flyweights[key];
}
// 获取当前缓存的享元对象数量
public int GetFlyweightCount()
{
return flyweights.Count;
}
}
// 具体游戏对象:包含不可共享的状态和共享的享元对象
public class GameSpriteObject : MonoBehaviour
{
private SpriteFlyweight flyweight; // 共享的享元部分
private Vector3 position; // 不可共享的位置
private Quaternion rotation; // 不可共享的旋转
private Color color; // 不可共享的颜色
// 初始化方法:组合享元对象和特有状态
public void Initialize(SpriteFlyweight flyweight, Vector3 pos, Quaternion rot, Color col)
{
this.flyweight = flyweight;
this.position = pos;
this.rotation = rot;
this.color = col;
// 应用精灵和属性到游戏对象
ApplySprite();
}
private void ApplySprite()
{
if (flyweight?.Sprite != null)
{
// 添加SpriteRenderer并设置共享的精灵
SpriteRenderer renderer = GetComponent<SpriteRenderer>();
if (renderer == null)
renderer = gameObject.AddComponent<SpriteRenderer>();
renderer.sprite = flyweight.Sprite;
transform.position = position;
transform.rotation = rotation;
renderer.color = color;
gameObject.name = $"{flyweight.SpriteName}_Instance";
}
}
}
// 客户端:使用享元模式创建大量游戏对象
public class FlyweightClient : MonoBehaviour
{
public Sprite[] sprites; // 可在Inspector中赋值的精灵数组
public int objectsPerType = 50; // 每种精灵创建的实例数量
private SpriteFlyweightFactory factory;
private List<GameSpriteObject> gameObjects = new List<GameSpriteObject>();
private void Start()
{
// 初始化享元工厂
factory = new SpriteFlyweightFactory();
// 创建大量游戏对象
CreateMultipleObjects();
// 显示统计信息
Debug.Log($"创建了 {gameObjects.Count} 个游戏对象");
Debug.Log($"但只使用了 {factory.GetFlyweightCount()} 个共享的享元对象");
}
private void CreateMultipleObjects()
{
foreach (Sprite sprite in sprites)
{
if (sprite == null) continue;
// 获取共享的享元对象
SpriteFlyweight flyweight = factory.GetFlyweight(sprite);
// 创建多个实例,共享同一个享元对象但拥有不同的位置和颜色
for (int i = 0; i < objectsPerType; i++)
{
// 随机位置和颜色(特有状态)
Vector3 randomPos = new Vector3(
Random.Range(-10f, 10f),
Random.Range(-5f, 5f),
0
);
Color randomColor = new Color(
Random.Range(0.8f, 1f),
Random.Range(0.8f, 1f),
Random.Range(0.8f, 1f)
);
// 创建游戏对象并初始化
GameObject go = new GameObject();
GameSpriteObject gameSprite = go.AddComponent<GameSpriteObject>();
gameSprite.Initialize(flyweight, randomPos, Quaternion.identity, randomColor);
gameObjects.Add(gameSprite);
}
}
}
}
享元模式的优势:
显著减少内存占用,尤其在需要创建大量相似对象时;降低了系统资源消耗,提高性能;将对象的共享数据和特有数据分离,清晰管理不同类型的状态;适合处理纹理、字体、配置数据等重复使用的资源。在 Unity 中,享元模式特别适合 2D 游戏中的大量精灵实例、粒子系统配置、地形瓦片等场景,能够有效优化内存使用和加载性能。
代理模式(Proxy):为对象提供 "代理类",控制对原对象的访问(如权限、缓存、远程调用)。
代理模式的相关具体代码如下所示:
cs
using UnityEngine;
using System;
// 主题接口:定义真实对象和代理的共同行为
public interface IHeavyResource
{
void Load();
void Unload();
bool IsLoaded { get; }
string GetResourceName();
}
// 真实主题:需要被代理的重量级资源(如大型模型、关卡数据等)
public class HeavyResource : IHeavyResource
{
private string resourcePath;
private bool isLoaded;
public HeavyResource(string path)
{
resourcePath = path;
isLoaded = false;
}
public void Load()
{
if (isLoaded)
{
Debug.Log($"{GetResourceName()} 已加载,无需重复加载");
return;
}
// 模拟加载重量级资源的耗时操作
Debug.Log($"开始加载重量级资源: {resourcePath}");
// 实际项目中可能是 Resources.Load() 或 Addressables.LoadAssetAsync()
System.Threading.Thread.Sleep(1000); // 模拟加载延迟
isLoaded = true;
Debug.Log($"{GetResourceName()} 加载完成");
}
public void Unload()
{
if (!isLoaded)
{
Debug.Log($"{GetResourceName()} 未加载,无需卸载");
return;
}
// 模拟卸载资源
Debug.Log($"卸载重量级资源: {resourcePath}");
// 实际项目中可能是 Resources.UnloadAsset() 或 Addressables.Release()
isLoaded = false;
}
public bool IsLoaded => isLoaded;
public string GetResourceName()
{
return System.IO.Path.GetFileName(resourcePath);
}
}
/// <summary>
/// 代理类(ResourceProxy):实现主题接口,内部持有真实资源的引用,提供以下额外功能:
/// 延迟初始化:只有在需要时才创建真实资源对象
/// 权限控制:检查访问者是否有权限操作资源
/// 访问日志:记录所有对资源的操作
/// 安全访问:即使真实对象未初始化也能安全处理请求
/// </summary>
// 代理类:控制对真实资源的访问
public class ResourceProxy : IHeavyResource
{
private HeavyResource realResource; // 真实资源对象(延迟初始化)
private string resourcePath;
private int accessCount = 0; // 记录访问次数
private string userRole; // 访问者角色(用于权限控制)
public ResourceProxy(string path, string role)
{
resourcePath = path;
userRole = role;
}
// 代理的加载方法:包含权限检查、延迟初始化和日志记录
public void Load()
{
accessCount++;
LogAccess("Load");
// 权限检查
if (!HasPermission())
{
Debug.LogError($"权限不足!{userRole} 无法加载 {GetResourceName()}");
return;
}
// 延迟初始化:只有在真正需要时才创建真实对象
if (realResource == null)
{
realResource = new HeavyResource(resourcePath);
}
realResource.Load();
}
// 代理的卸载方法
public void Unload()
{
accessCount++;
LogAccess("Unload");
if (!HasPermission())
{
Debug.LogError($"权限不足!{userRole} 无法卸载 {GetResourceName()}");
return;
}
if (realResource != null)
{
realResource.Unload();
}
else
{
Debug.Log($"{GetResourceName()} 尚未加载,无法卸载");
}
}
public bool IsLoaded
{
get
{
// 即使真实对象未初始化,也能安全返回状态
return realResource != null && realResource.IsLoaded;
}
}
public string GetResourceName()
{
return System.IO.Path.GetFileName(resourcePath);
}
// 代理特有的功能:权限检查
private bool HasPermission()
{
// 只有管理员和开发者可以加载特殊资源
if (resourcePath.Contains("secret") &&
userRole != "Admin" &&
userRole != "Developer")
{
return false;
}
return true;
}
// 代理特有的功能:访问日志记录
private void LogAccess(string operation)
{
Debug.Log($"[{DateTime.Now:HH:mm:ss}] {userRole} 执行 {operation} 操作,资源: {GetResourceName()},累计访问: {accessCount}次");
}
}
// 客户端:只与代理交互,不直接访问真实对象
public class ResourceManager : MonoBehaviour
{
private void Start()
{
// 创建代理(客户端不知道真实对象的存在)
IHeavyResource normalResource = new ResourceProxy("models/character.prefab", "Player");
IHeavyResource secretResource = new ResourceProxy("models/secret_boss.prefab", "Player");
IHeavyResource adminResource = new ResourceProxy("models/secret_boss.prefab", "Admin");
// 通过代理访问资源
Debug.Log("=== 玩家访问普通资源 ===");
normalResource.Load();
normalResource.Load(); // 测试重复加载
Debug.Log($"普通资源加载状态: {normalResource.IsLoaded}");
normalResource.Unload();
Debug.Log("\n=== 玩家访问机密资源(权限不足) ===");
secretResource.Load(); // 会被拒绝
Debug.Log("\n=== 管理员访问机密资源 ===");
adminResource.Load();
Debug.Log($"机密资源加载状态: {adminResource.IsLoaded}");
adminResource.Unload();
}
}
代理模式的优势:
控制对真实对象的访问,可实现权限管理、访问限制;实现延迟加载,提高系统启动速度和内存使用效率;可以在不修改真实对象的情况下添加额外功能(如日志、缓存);隔离了客户端与真实对象,降低了耦合度。在 Unity 中,代理模式适合用于资源管理、网络请求、权限控制等场景,尤其适合处理那些创建成本高、加载耗时的资源或服务。