关于 Unity Architecture
介绍可维护 Unity 项目的架构模式,面向 Unity 游戏开发者,帮助组织系统、数据与通信,并保持组件易于测试、低耦合。
主要功能
- 01管理器与服务定位器模式
- 02基于 ScriptableObject 的数据设计
- 03事件驱动通信模式
- 04组件组合与状态机
- 05MVC/MVP 分层
适用场景
- 01组织游戏全局系统
- 02跨场景共享游戏数据
- 03解耦游戏逻辑与 UI 组件
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ cryptorabea/ claude_unity_dev_plugin --skill unity-architecture安装后在 Claude Code 中输入/unity-architecture确认已加载。
附带文件references/manager-patterns.mdreferences/scriptableobject-architecture.md
详细介绍
Unity 游戏架构
适用于可扩展、易维护 Unity 项目的核心架构模式和设计原则。
概述
良好的架构能够分离关注点、降低耦合,并使代码易于测试和维护。本技能介绍经过验证的 Unity 游戏开发模式。
核心架构概念:
- 管理器模式和全局系统
- 基于 ScriptableObject 的数据架构
- 事件驱动通信
- 组件组合模式
- 依赖管理
管理器模式
用于协调全局游戏功能的集中式系统。
单例管理器
最常见的全局管理器模式:
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
public void StartGame() { }
public void PauseGame() { }
public void EndGame() { }
}
// Access from anywhere
public class Player : MonoBehaviour
{
private void Start()
{
GameManager.Instance.StartGame();
}
}
适用场景:
- 游戏状态管理(GameManager)
- 音频管理(AudioManager)
- 输入管理(InputManager)
- 存档/读档系统(SaveManager)
- UI 管理(UIManager)
不适用场景:
- 所有情况(避免“单例地狱”)
- 临时系统
- 需要多个实例的系统
泛型单例基类
可复用的单例模式:
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<T>();
if (instance == null)
{
GameObject singleton = new GameObject(typeof(T).Name);
instance = singleton.AddComponent<T>();
DontDestroyOnLoad(singleton);
}
}
return instance;
}
}
protected virtual void Awake()
{
if (instance == null)
{
instance = this as T;
DontDestroyOnLoad(gameObject);
}
else if (instance != this)
{
Destroy(gameObject);
}
}
}
// Usage
public class GameManager : Singleton<GameManager>
{
protected override void Awake()
{
base.Awake();
// Additional initialization
}
}
管理器初始化顺序
控制管理器初始化:
// Use Script Execution Order:
// Edit > Project Settings > Script Execution Order
// Or explicit initialization
public class GameBootstrap : MonoBehaviour
{
private void Awake()
{
InitializeManagers();
}
private void InitializeManagers()
{
// Initialize in specific order
var saveManager = SaveManager.Instance;
var audioManager = AudioManager.Instance;
var gameManager = GameManager.Instance;
// Managers initialize in Awake, but access here ensures order
}
}
最佳实践:使用显式初始化场景或引导程序。
服务定位器模式
用于依赖注入的单例替代方案:
public class ServiceLocator
{
private static ServiceLocator instance;
public static ServiceLocator Instance => instance ?? (instance = new ServiceLocator());
private readonly Dictionary<Type, object> services = new Dictionary<Type, object>();
public void RegisterService<T>(T service)
{
services[typeof(T)] = service;
}
public T GetService<T>()
{
if (services.TryGetValue(typeof(T), out var service))
{
return (T)service;
}
throw new Exception($"Service {typeof(T)} not found");
}
}
// Register services
public class GameBootstrap : MonoBehaviour
{
private void Awake()
{
var audioManager = new AudioManager();
ServiceLocator.Instance.RegisterService(audioManager);
var saveManager = new SaveManager();
ServiceLocator.Instance.RegisterService(saveManager);
}
}
// Access services
public class Player : MonoBehaviour
{
private void Start()
{
var audio = ServiceLocator.Instance.GetService<AudioManager>();
audio.PlaySound("Jump");
}
}
相较于单例的优点:
- 可测试(可注入模拟服务)
- 无静态依赖
- 依赖关系清晰
缺点:
- 需要编写更多设置代码
- 运行时字典查找
- 不易发现
ScriptableObject 架构
使用 ScriptableObject 的数据驱动设计。
ScriptableObject 数据容器
将数据与行为分开存储:
[CreateAssetMenu(fileName = "WeaponData", menuName = "Game/Weapon Data")]
public class WeaponData : ScriptableObject
{
public string weaponName;
public int damage;
public float fireRate;
public Sprite icon;
public GameObject projectilePrefab;
}
public class Weapon : MonoBehaviour
{
[SerializeField] private WeaponData data;
public void Fire()
{
Instantiate(data.projectilePrefab, firePoint.position, firePoint.rotation);
}
public int GetDamage() => data.damage;
}
优点:
- 数据与代码分离
- 在多个场景间共享数据
- 无需更改代码即可编辑
- 方便设计师使用
适用场景:
- 物品/武器属性
- 角色数据
- 游戏平衡数值
- 配置
ScriptableObject 事件
使用 ScriptableObject 的事件系统:
[CreateAssetMenu(fileName = "GameEvent", menuName = "Events/Game Event")]
public class GameEvent : ScriptableObject
{
private readonly List<GameEventListener> listeners = new List<GameEventListener>();
public void Raise()
{
for (int i = listeners.Count - 1; i >= 0; i--)
{
listeners[i].OnEventRaised();
}
}
public void RegisterListener(GameEventListener listener)
{
if (!listeners.Contains(listener))
listeners.Add(listener);
}
public void UnregisterListener(GameEventListener listener)
{
listeners.Remove(listener);
}
}
public class GameEventListener : MonoBehaviour
{
[SerializeField] private GameEvent gameEvent;
[SerializeField] private UnityEvent response;
private void OnEnable()
{
gameEvent.RegisterListener(this);
}
private void OnDisable()
{
gameEvent.UnregisterListener(this);
}
public void OnEventRaised()
{
response.Invoke();
}
}
用法:
Create GameEvent asset: "OnPlayerDeath"
Attach GameEventListener to UI
Configure response: Show death screen
Raise event when player dies
优点:
- 设计师可直接使用
- 可在 Inspector 中可视化连接
- 系统间解耦
- 事件可复用
ScriptableObject 变量
在多个场景间共享变量:
public abstract class ScriptableVariable<T> : ScriptableObject
{
[SerializeField] private T value;
public T Value
{
get => value;
set
{
this.value = value;
OnValueChanged?.Invoke(value);
}
}
public event Action<T> OnValueChanged;
}
[CreateAssetMenu(fileName = "IntVariable", menuName = "Variables/Int")]
public class IntVariable : ScriptableVariable<int> { }
[CreateAssetMenu(fileName = "FloatVariable", menuName = "Variables/Float")]
public class FloatVariable : ScriptableVariable<float> { }
用法:
public class Player : MonoBehaviour
{
[SerializeField] private IntVariable playerHealth;
public void TakeDamage(int damage)
{
playerHealth.Value -= damage; // Updates all subscribers
}
}
public class HealthUI : MonoBehaviour
{
[SerializeField] private IntVariable playerHealth;
[SerializeField] private Text healthText;
private void OnEnable()
{
playerHealth.OnValueChanged += UpdateUI;
UpdateUI(playerHealth.Value);
}
private void OnDisable()
{
playerHealth.OnValueChanged -= UpdateUI;
}
private void UpdateUI(int health)
{
healthText.text = $"Health: {health}";
}
}
优点:
- 在多个场景间持久保存
- 支持多个监听器
- 可在 Inspector 中编辑
- 运行时更改会反映到所有位置
事件系统
组件间的解耦通信。
C# 事件
标准 C# 事件模式:
public class Health : MonoBehaviour
{
public event Action<int> OnHealthChanged;
public event Action OnDeath;
private int health = 100;
public void TakeDamage(int damage)
{
health -= damage;
OnHealthChanged?.Invoke(health);
if (health <= 0)
{
OnDeath?.Invoke();
}
}
}
public class HealthUI : MonoBehaviour
{
[SerializeField] private Health playerHealth;
private void OnEnable()
{
playerHealth.OnHealthChanged += UpdateHealthBar;
playerHealth.OnDeath += ShowDeathScreen;
}
private void OnDisable()
{
playerHealth.OnHealthChanged -= UpdateHealthBar;
playerHealth.OnDeath -= ShowDeathScreen;
}
private void UpdateHealthBar(int health) { }
private void ShowDeathScreen() { }
}
重要:务必在 OnDisable 中取消订阅,以防止内存泄漏。
UnityEvent
可在 Inspector 中分配的事件:
public class Interactable : MonoBehaviour
{
[SerializeField] private UnityEvent onInteract;
[SerializeField] private UnityEvent<int> onScoreChanged;
public void Interact()
{
onInteract?.Invoke();
}
public void AddScore(int points)
{
onScoreChanged?.Invoke(points);
}
}
优点:
- 设计师可在 Inspector 中使用
- 简单交互无需编写代码
- 可视化连接
缺点:
- 比 C# 事件慢
- 无编译时检查
- 更难调试
适用场景:
- 简单交互
- 由设计师驱动的事件
- 原型开发
全局事件总线
集中式事件系统:
public static class EventBus
{
private static readonly Dictionary<Type, Delegate> eventTable = new Dictionary<Type, Delegate>();
public static void Subscribe<T>(Action<T> handler)
{
if (eventTable.TryGetValue(typeof(T), out var existingHandler))
{
eventTable[typeof(T)] = Delegate.Combine(existingHandler, handler);
}
else
{
eventTable[typeof(T)] = handler;
}
}
public static void Unsubscribe<T>(Action<T> handler)
{
if (eventTable.TryGetValue(typeof(T), out var existingHandler))
{
var newHandler = Delegate.Remove(existingHandler, handler);
if (newHandler == null)
eventTable.Remove(typeof(T));
else
eventTable[typeof(T)] = newHandler;
}
}
public static void Publish<T>(T eventData)
{
if (eventTable.TryGetValue(typeof(T), out var handler))
{
(handler as Action<T>)?.Invoke(eventData);
}
}
}
// Event data types
public struct PlayerDiedEvent
{
public Vector3 position;
public string killedBy;
}
// Subscribe
public class DeathUI : MonoBehaviour
{
private void OnEnable()
{
EventBus.Subscribe<PlayerDiedEvent>(OnPlayerDied);
}
private void OnDisable()
{
EventBus.Unsubscribe<PlayerDiedEvent>(OnPlayerDied);
}
private void OnPlayerDied(PlayerDiedEvent data)
{
ShowDeathScreen(data.position, data.killedBy);
}
}
// Publish
public class Player : MonoBehaviour
{
private void Die()
{
EventBus.Publish(new PlayerDiedEvent
{
position = transform.position,
killedBy = "Enemy"
});
}
}
优点:
- 完全解耦
- 无需直接引用
- 使用结构体时具备类型安全
缺点:
- 运行时开销(字典查找)
- 更难追踪事件流
- 忘记取消订阅时存在内存泄漏风险
组件组合模式
策略模式
可互换的行为:
public interface IMovementStrategy
{
void Move(Transform transform, float speed);
}
public class GroundMovement : MonoBehaviour, IMovementStrategy
{
public void Move(Transform transform, float speed)
{
// Ground-based movement
}
}
public class FlyingMovement : MonoBehaviour, IMovementStrategy
{
public void Move(Transform transform, float speed)
{
// Flying movement
}
}
public class Character : MonoBehaviour
{
[SerializeField] private float speed = 5f;
private IMovementStrategy movementStrategy;
private void Awake()
{
movementStrategy = GetComponent<IMovementStrategy>();
}
private void Update()
{
movementStrategy.Move(transform, speed);
}
}
优点:
- 可在运行时切换行为
- 策略可复用
- 开闭原则
状态机模式
管理对象状态:
public interface IState
{
void Enter();
void Execute();
void Exit();
}
public class IdleState : MonoBehaviour, IState
{
public void Enter() => Debug.Log("Entering Idle");
public void Execute() { }
public void Exit() => Debug.Log("Exiting Idle");
}
public class StateMachine : MonoBehaviour
{
private IState currentState;
public void ChangeState(IState newState)
{
currentState?.Exit();
currentState = newState;
currentState?.Enter();
}
private void Update()
{
currentState?.Execute();
}
}
public class Enemy : MonoBehaviour
{
private StateMachine stateMachine;
private IdleState idleState;
private ChaseState chaseState;
private void Awake()
{
stateMachine = GetComponent<StateMachine>();
idleState = GetComponent<IdleState>();
chaseState = GetComponent<ChaseState>();
stateMachine.ChangeState(idleState);
}
public void OnPlayerSpotted()
{
stateMachine.ChangeState(chaseState);
}
}
观察者模式
一对多通知:
public interface IObserver
{
void OnNotify(string eventType);
}
public class Subject : MonoBehaviour
{
private readonly List<IObserver> observers = new List<IObserver>();
public void Attach(IObserver observer)
{
if (!observers.Contains(observer))
observers.Add(observer);
}
public void Detach(IObserver observer)
{
observers.Remove(observer);
}
protected void Notify(string eventType)
{
for (int i = observers.Count - 1; i >= 0; i--)
{
observers[i].OnNotify(eventType);
}
}
}
public class Player : Subject
{
public void Jump()
{
Notify("PlayerJumped");
}
}
public class AudioObserver : MonoBehaviour, IObserver
{
[SerializeField] private Player player;
private void OnEnable()
{
player.Attach(this);
}
private void OnDisable()
{
player.Detach(this);
}
public void OnNotify(string eventType)
{
if (eventType == "PlayerJumped")
PlayJumpSound();
}
}
Unity 中的 MVC/MVP
模型-视图-控制器
分离数据、表现和逻辑:
// Model - Data
public class PlayerModel
{
public int Health { get; private set; } = 100;
public int Score { get; private set; } = 0;
public event Action<int> OnHealthChanged;
public event Action<int> OnScoreChanged;
public void TakeDamage(int damage)
{
Health -= damage;
OnHealthChanged?.Invoke(Health);
}
public void AddScore(int points)
{
Score += points;
OnScoreChanged?.Invoke(Score);
}
}
// View - Presentation
public class PlayerView : MonoBehaviour
{
[SerializeField] private Text healthText;
[SerializeField] private Text scoreText;
public void UpdateHealth(int health)
{
healthText.text = $"Health: {health}";
}
public void UpdateScore(int score)
{
scoreText.text = $"Score: {score}";
}
}
// Controller - Logic
public class PlayerController : MonoBehaviour
{
private PlayerModel model;
private PlayerView view;
private void Awake()
{
model = new PlayerModel();
view = GetComponent<PlayerView>();
model.OnHealthChanged += view.UpdateHealth;
model.OnScoreChanged += view.UpdateScore;
}
private void OnDestroy()
{
model.OnHealthChanged -= view.UpdateHealth;
model.OnScoreChanged -= view.UpdateScore;
}
public void TakeDamage(int damage)
{
model.TakeDamage(damage);
}
public void AddScore(int points)
{
model.AddScore(points);
}
}
优点:
- 可测试(可模拟模型/视图)
- 组件可复用
- 分工清晰
适用场景:
- 复杂 UI
- 需要可测试的代码
- 同一数据存在多个视图
其他资源
参考文件
有关详细的架构模式,请参阅:
references/manager-patterns.md- Manager 的实现、初始化和通信references/scriptableobject-architecture.md- 高级 SO 模式、运行时集合和变量references/event-systems.md- 事件模式、消息总线和发布-订阅系统references/design-patterns.md- Unity 中的工厂、对象池、命令和策略模式
最佳实践
✅ 应该:
- 使用 Manager 管理全局系统(音频、输入、存档)
- 利用 ScriptableObjects 存储数据
- 实现事件驱动通信
- 优先采用组合而非继承
- 始终在 OnDisable 中取消订阅事件
- 使用依赖注入以便于测试
- 让 Manager 专注于单一职责
❌ 不应该:
- 把所有东西都做成单例
- 创建上帝 Manager(应只处理一个关注点)
- 忘记取消订阅事件
- 硬编码依赖项
- 什么都用单例
- 混合数据和呈现
- 创建深层继承层次结构
黄金法则:为变化而设计。松耦合、高内聚的系统更易于维护、测试和扩展。
应用这些架构模式,构建可扩展、易维护且经得起时间考验的 Unity 项目。
评论
支持 Markdown