
1. 项目概述为什么我们需要一个高可扩展的游戏框架如果你在Unity里做过几个项目尤其是参与过团队协作大概率会遇到这样的场景项目初期大家图快所有逻辑都往MonoBehaviour里塞UI、数据、网络、逻辑搅在一起。到了中期功能越加越多改一个按钮的响应可能要翻五六个脚本牵一发而动全身。到了后期新来的同事面对一坨“祖传代码”无从下手任何改动都像在走钢丝bug层出不穷。这就是缺乏架构设计的典型后果——代码变成了“意大利面条”难以维护、难以扩展、难以测试。“Unity架构设计实战从零搭建高可扩展性游戏框架”这个标题直指的就是这个痛点。它不是一个简单的功能实现教程而是一套工程化的解决方案。其核心目标是构建一个职责清晰、模块解耦、易于替换和扩展的代码结构让项目能够从容应对需求变更、团队协作和长期维护。高可扩展性意味着当你需要增加一个新的游戏系统比如从单机变为联网、替换一个底层服务比如换用不同的资源加载方案或者仅仅是调整UI布局时你的改动可以被控制在最小的、最明确的范围内而不会引发不可预知的连锁反应。这份实战指南适合所有希望提升项目工程化水平的Unity开发者无论你是独立开发者还是团队中的核心程序。它将带你从最基础的“分层”思想开始一步步搭建一个坚实的框架基础并附上清晰的分层设计图作为“蓝图”。接下来我将结合我多年踩坑和重构的经验为你拆解这个框架的构建全过程从设计思路到每一层的具体实现再到那些只有实际做过才知道的“坑”和技巧。2. 框架整体设计与核心思路拆解2.1 分层架构从混沌到秩序的核心思想分层是解决复杂软件系统耦合度的经典手段。其核心思想是“关注点分离”将整个应用按照职责的不同划分为多个水平层次。每一层只与它相邻的上下层进行通信层与层之间通过定义良好的接口进行交互从而降低依赖提高内聚。对于Unity游戏项目一个经典且实用的分层模型通常包含以下四层从下至上数据层负责数据的定义、存储、序列化与反序列化。它不关心数据从哪里来、到哪里去只关心数据本身的结构和持久化。服务层提供可复用的、与具体游戏逻辑无关的基础服务。例如资源加载、网络通信、音频管理、本地化、日志系统等。它们是整个框架的“公共设施”。逻辑层这是游戏业务逻辑的核心。它包含游戏规则、状态管理、角色AI、战斗计算等。逻辑层通过调用服务层的能力并操作数据层的数据来实现游戏功能。表现层负责一切与玩家交互和视觉/听觉呈现相关的内容。包括UI界面、角色动画、特效、摄像机控制、输入响应等。表现层是逻辑层的“代言人”它监听逻辑层状态的变化并将其以直观的形式展现给玩家。这种分层结构带来了几个显著优势可测试性你可以轻松地 Mock模拟服务层或表现层单独测试逻辑层的正确性。可替换性如果想更换UI框架如从UGUI换到UIToolkit你只需要重写表现层中与UI相关的部分逻辑层几乎无需改动。并行开发团队成员可以基于定义好的层间接口并行开发不同层次的内容减少等待和冲突。代码清晰新功能应该放在哪一层有了明确的指导避免了随意堆放。2.2 核心设计原则指导我们做出每一个选择在具体搭建之前我们需要确立几个贯穿始终的设计原则它们是我们做技术选型和代码组织的“宪法”。依赖倒置原则这是分层架构的基石。高层模块如逻辑层不应该依赖低层模块如具体的网络库二者都应该依赖其抽象接口。例如逻辑层只依赖一个INetworkService接口而不关心底层用的是Socket还是WebSocket。这通过依赖注入容器如Zenject、VContainer来实现。单一职责原则一个类、一个模块甚至一个方法应该只有一个引起它变化的原因。这迫使我们将庞大的功能拆分成小而专注的单元。开闭原则对扩展开放对修改关闭。当需要增加新功能如一种新的怪物类型时应通过添加新的代码如继承一个新的Monster类来实现而不是修改已有的、稳定的代码。面向接口编程层与层之间、模块与模块之间通过接口进行通信。这极大地降低了耦合度使得替换实现变得轻而易举。基于这些原则我们的框架将不是一个“大而全”的庞然大物而是一个由许多松散耦合、通过接口连接的“乐高积木”组成的生态系统。接下来我们就进入每一层的具体构建。3. 数据层定义游戏的基石数据层是框架最稳定的一层它定义了游戏世界的“原子”。这一层设计得好上层建筑才会稳固。3.1 数据模型设计从配置表到运行时对象游戏数据通常来源于策划配置的Excel或JSON表格。我们需要一个机制将这些静态配置转化为运行时易于操作的对象。方案选择ScriptableObject vs 纯C#类ScriptableObjectUnity原生支持可在编辑器内可视化编辑和引用非常适合存放游戏设计参数如角色基础属性、物品信息、关卡数据。它的数据作为Asset存在方便策划独立调整。纯C#类更轻量序列化/反序列化性能更好适合网络传输或需要频繁创建销毁的临时数据。实操建议混合使用。将静态的、设计期的配置数据放在ScriptableObject中。在游戏启动时通过一个DataManager服务将这些ScriptableObject的数据加载并转换为纯C#类的运行时数据模型。这样做既利用了编辑器的便利性又保证了运行时的效率。例如一个物品的配置可能如下// 数据层定义模型 [System.Serializable] public class ItemConfig : ScriptableObject { public int itemId; public string itemName; public string iconPath; public int maxStackCount; // ... 其他配置属性 } // 运行时数据模型 public class RuntimeItemData { public int ItemId { get; private set; } public string ItemName { get; private set; } public Sprite Icon { get; set; } // 可能由资源服务异步加载 public int CurrentCount { get; set; } // ... 其他运行时状态 }3.2 数据存取与管理集中化的数据枢纽我们不应该让逻辑层或表现层直接去读取ScriptableObject或JSON文件。应该由一个专门的IDataService来负责。这个服务的主要职责包括加载与缓存在适当的时机如游戏启动、进入场景时加载所有必要的配置数据并进行缓存避免重复IO操作。提供查询接口提供类似GetItemConfigById(int id)、GetAllSkillConfigs()这样的方法供上层按需获取数据。管理运行时数据管理玩家存档、游戏会话状态等动态数据。这部分数据通常需要序列化到本地或上传到服务器。注意事项数据加载通常是异步操作。确保你的IDataService提供异步加载接口如返回Task或UniTask并在数据准备就绪前阻止依赖它的逻辑执行。一个常见的做法是设计一个“启动流程”确保所有基础服务的数据加载完成后再进入游戏主循环。4. 服务层构建稳固的公共设施服务层是框架的“工具箱”它封装了所有与具体游戏逻辑无关的、可复用的功能。设计良好的服务应该是无状态的、线程安全的如果涉及多线程并且通过接口暴露。4.1 典型服务设计与实现资源管理服务直接使用Resources.Load或AssetBundleAPI 会使得资源加载逻辑散落在各处难以管理和优化。我们需要一个IResourceService。职责提供统一的资源加载、卸载、缓存和生命周期管理。关键设计异步加载优先所有加载接口都应是异步的避免卡顿。引用计数对加载的资源进行引用计数当没有任何逻辑引用时自动卸载防止内存泄漏。提供多种加载方式支持通过AssetName、Addressables的Address、或自定义Key进行加载。与数据层结合RuntimeItemData.Icon的加载就可以委托给IResourceService.LoadAsyncSprite(iconPath)。网络通信服务根据你的游戏类型强联网、弱联网、单机网络层的设计差异很大。但抽象出一个INetworkService接口是必要的。职责处理连接管理、消息的发送与接收、协议编解码、心跳与重连。分层设计正如网络热词中提到的可以将网络层进一步拆分为传输层TCP/UDP/WebSocket和应用层协议如自定义二进制协议、Protobuf、MessagePack。INetworkService关注应用层底层传输可以封装在具体的实现类里如TcpNetworkChannel。与逻辑层通信通常采用事件或回调机制。网络层收到消息后反序列化为C#对象然后触发一个事件如OnPlayerDataUpdated逻辑层监听此事件并做出响应。音频管理服务一个常见的坑是多个音效同时播放时的管理和优先级问题。IAudioService可以解决。职责播放背景音乐、音效管理音频混合如战斗时压低背景音乐提供音量控制接口。实现要点在Unity中通常通过管理一组AudioSource组件池来实现避免频繁的GameObject创建销毁。4.2 服务的注册与获取依赖注入容器的运用服务之间、服务与上层之间也存在依赖。手动管理这些依赖new对象并传递在大型项目中会变得极其繁琐和脆弱。这时就需要一个依赖注入容器。以VContainer为例它性能好与Unity集成度高定义接口和实现public interface IResourceService { ... } public class ResourceService : IResourceService { ... }在 Composition Root 注册通常在游戏启动的第一个场景或一个专门的启动器GameLauncher中。public class GameLifetimeScope : LifetimeScope { protected override void Configure(IContainerBuilder builder) { // 注册服务通常以单例模式 builder.RegisterIResourceService, ResourceService(Lifetime.Singleton); builder.RegisterINetworkService, TcpNetworkService(Lifetime.Singleton); builder.RegisterIAudioService, AudioService(Lifetime.Singleton); // 注册数据服务 builder.RegisterIDataService, DataService(Lifetime.Singleton); // 注册逻辑层管理器后面会讲 builder.RegisterGameManager(Lifetime.Singleton).AsSelf(); } }在需要的地方注入容器会自动解析依赖并注入。public class PlayerLogic { private readonly IDataService _dataService; private readonly INetworkService _networkService; // 通过构造函数注入 public PlayerLogic(IDataService dataService, INetworkService networkService) { _dataService dataService; _networkService networkService; } }实操心得依赖注入容器是框架的“粘合剂”它让分层架构变得可行。强烈建议在项目初期就引入。它不仅能解耦还能极大简化单元测试的编写因为你可以轻松地为测试环境注入Mock服务。5. 逻辑层游戏规则与状态的中枢逻辑层是游戏的“大脑”它包含了所有的游戏规则和状态。这一层应该完全独立于Unity的引擎对象如GameObject,MonoBehaviour理想情况下它甚至可以被编译成一个独立的 .NET 类库这保证了其纯粹性和可测试性。5.1 状态管理有限状态机与事件驱动游戏逻辑的核心是状态管理。角色的行为、游戏的流程都可以看作是状态的变化。有限状态机对于角色AI、UI界面、游戏整体流程如登录、主城、战斗FSM是非常合适的模型。你可以实现一个通用的StateMachine类和IState接口。public interface IState { void OnEnter(); void OnUpdate(float deltaTime); void OnExit(); } public class StateMachine { private IState _currentState; public void ChangeState(IState newState) { ... } }这样一个PlayerAI就可以拥有IdleState、ChaseState、AttackState等逻辑清晰易于扩展。事件驱动架构逻辑层内部、以及逻辑层与表现层之间应尽量避免直接的函数调用而是通过事件进行通信。这进一步降低了模块间的耦合度。定义事件使用class GameEvent { }或利用C#的event关键字也可以使用更强大的消息总线库。发布/订阅当玩家获得物品时逻辑层发布一个ItemAcquiredEvent。背包系统、任务系统、UI界面都可以独立订阅这个事件并做出自己的反应而玩家逻辑本身不需要知道有哪些系统关心这个事件。5.2 核心管理器组织你的游戏世界逻辑层通常由几个核心的“管理器”组成它们各司其职GameManager游戏总控管理游戏流程状态机如启动、运行、暂停、结束。PlayerManager管理玩家角色数据、装备、技能等。BattleManager处理战斗相关的计算伤害公式、Buff/Debuff效果等。QuestManager管理任务链的接受、进行、完成。InventoryManager管理背包、仓库物品的增删改查。这些管理器都通过依赖注入获取它们需要的服务如IDataService并通过事件总线与其他管理器通信。它们持有并操作着游戏的核心数据状态。避坑指南警惕“管理器地狱”。不要为每一个细小功能都创建一个管理器。管理器的划分应基于功能的聚合度和职责的边界。如果一个管理器变得过于庞大超过1000行考虑是否应该将其拆分成更小、更专注的子系统。6. 表现层连接逻辑与玩家的桥梁表现层是唯一与Unity引擎GameObject、MonoBehaviour、UGUI/UIToolkit等紧密相关的一层。它的职责是“反映”逻辑层的状态并“转发”玩家的输入。6.2 UI框架MVVM模式的应用UI是表现层中最复杂的部分之一。采用Model-View-ViewModel模式可以很好地分离UI逻辑与显示。Model就是逻辑层中的数据例如PlayerData。ViewModel一个为View量身定制的数据模型。它从Model中获取数据并转换为View可以直接绑定的属性通常是实现了INotifyPropertyChanged接口的类。public class PlayerHudViewModel : INotifyPropertyChanged { private PlayerData _model; public int Hp { get _model.CurrentHp; set { if (_model.CurrentHp ! value) { _model.CurrentHp value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }ViewUnity中的MonoBehaviour挂载在UI预制体上。它持有对ViewModel的引用并通过数据绑定可以使用Unity的UnityEvent或第三方绑定插件或自己写简单的绑定脚本将UI元素如Text,Slider与ViewModel的属性关联起来。public class PlayerHudView : MonoBehaviour { [SerializeField] private Text _hpText; [SerializeField] private Slider _hpSlider; private PlayerHudViewModel _viewModel; public void Bind(PlayerHudViewModel viewModel) { _viewModel viewModel; _viewModel.PropertyChanged OnViewModelPropertyChanged; UpdateView(); } private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName nameof(PlayerHudViewModel.Hp)) UpdateView(); } private void UpdateView() { _hpText.text ${_viewModel.Hp}/{_viewModel.MaxHp}; _hpSlider.value (float)_viewModel.Hp / _viewModel.MaxHp; } }这样当逻辑层修改了PlayerData.CurrentHp并通知了PlayerHudViewModelUI就会自动更新。View只关心如何显示不关心数据怎么来的ViewModel负责数据转换和通知Model逻辑层数据则保持纯净。6.2 角色与动画控制对于3D/2D角色表现层同样遵循“反映状态”的原则。角色控制器一个CharacterView组件挂在角色GameObject上。它订阅逻辑层Character对象发出的事件如OnMove,OnAttack,OnHpChanged。动画状态机使用Unity的Animator Controller。CharacterView根据接收到的逻辑事件设置Animator的参数如SetFloat(“Speed”, velocity),SetTrigger(“Attack”)驱动动画播放。特效与音效在CharacterView中根据逻辑事件播放对应的特效Instantiate一个预制体和调用IAudioService.PlaySound(“attack_sound”)。关键技巧表现层到逻辑层的通信必须是单向的。即逻辑层 -事件- 表现层。表现层不应直接修改逻辑层的数据。当玩家点击一个“使用道具”按钮时View应调用一个命令如ICommand模式或直接调用逻辑层InventoryManager的UseItem方法由逻辑层去改变数据然后逻辑层再发布事件通知表现层更新。这保证了数据流的清晰和可预测。7. 分层设计图解析与模块通信结合以上各层我们可以绘制出一张清晰的分层设计图此处以文字描述其结构[表现层 (Presentation Layer)] |-- UI系统 (MVVM: Views, ViewModels) |-- 角色/场景表现 (GameObject, Animator, ParticleSystem) |-- 输入监听与转发 | | (调用命令/方法监听事件) ↓ [逻辑层 (Logic Layer)] |-- 游戏管理器 (GameManager, BattleManager, PlayerManager...) |-- 游戏规则与状态机 |-- 领域模型 (Player, Monster, Item...) | | (调用服务接口操作数据模型) ↓ [服务层 (Service Layer)] |-- 资源服务 (IResourceService) |-- 网络服务 (INetworkService) |-- 音频服务 (IAudioService) |-- 本地化服务 (ILocalizationService) |-- 日志服务 (ILogger) | | (提供数据访问接口) ↓ [数据层 (Data Layer)] |-- 运行时数据模型 (RuntimeItemData, PlayerSaveData...) |-- 静态配置数据 (ScriptableObject/JSON Configs) |-- 数据存取服务 (IDataService) | 依赖注入容器 (如 VContainer) 贯穿各层负责对象的创建和依赖注入。 事件总线/消息系统 贯穿逻辑层与表现层负责松耦合的通信。模块间通信流程示例玩家拾取物品表现层玩家角色碰撞体触发OnTriggerEnter。表现层CharacterView检测到碰撞调用逻辑层PlayerManager.PickUpItem(itemId)命令。逻辑层PlayerManager方法内通过IDataService查询物品配置修改PlayerData的背包数据。逻辑层PlayerManager发布ItemAcquiredEvent事件包含物品ID和数量。逻辑层QuestManager监听了此事件检查是否有相关任务更新进度。逻辑层AchievementManager监听了此事件检查是否解锁成就。表现层InventoryHudViewModel监听了此事件更新背包UI的显示数据。表现层FloatingTextManager监听了此事件在玩家头顶生成一个“获得XXX”的飘字。整个过程拾取动作的发起方表现层只与PlayerManager有直接调用关系而后续的所有反应都是通过事件广播出去的实现了高度的解耦。8. 实战搭建步骤与核心代码示例让我们从一个最小的可运行示例开始搭建这个框架的骨架。8.1 第一步项目结构与基础设置在Unity中创建如下目录结构Assets/ ├── Scripts/ │ ├── Core/ # 框架核心与具体游戏无关 │ │ ├── Interfaces/ # 所有服务接口定义 │ │ ├── Events/ # 通用事件定义 │ │ └── Utils/ # 通用工具类 │ ├── Data/ # 数据层 │ │ ├── Models/ # 数据模型 │ │ ├── Configs/ # ScriptableObject配置 │ │ └── Services/ # IDataService实现 │ ├── Services/ # 服务层具体实现 │ │ ├── Resource/ │ │ ├── Audio/ │ │ └── ... │ ├── Logic/ # 逻辑层 │ │ ├── Managers/ │ │ ├── Systems/ # 战斗、任务等子系统 │ │ └── States/ # 游戏状态机 │ └── Presentation/ # 表现层 │ ├── UI/ │ ├── Characters/ │ └── ... └── Resources/ # 存放需Resources.Load的资源安装依赖注入容器。通过Unity Package Manager的Git URL安装VContainerhttps://github.com/hadashiA/VContainer.git?pathVContainer/Assets/VContainer。8.2 第二步实现一个最简单的服务-数据-逻辑链我们以实现一个“玩家点击按钮加载并显示角色名”为例。数据层创建角色配置。// Assets/Scripts/Data/Configs/CharacterConfig.asset (通过Create菜单创建) [CreateAssetMenu(fileName CharacterConfig, menuName Game/CharacterConfig)] public class CharacterConfig : ScriptableObject { public int characterId; public string characterName; public int maxHp; }服务层实现IDataService。// Assets/Scripts/Core/Interfaces/IDataService.cs public interface IDataService { T GetConfigT(string configName) where T : ScriptableObject; } // Assets/Scripts/Services/Data/DataService.cs public class DataService : IDataService { private Dictionarystring, ScriptableObject _configCache new(); public T GetConfigT(string configName) where T : ScriptableObject { if (!_configCache.TryGetValue(configName, out var config)) { config Resources.LoadT(configName); _configCache[configName] config; } return config as T; } }逻辑层创建PlayerManager。// Assets/Scripts/Logic/Managers/PlayerManager.cs public class PlayerManager { private readonly IDataService _dataService; private CharacterConfig _currentCharacterConfig; public PlayerManager(IDataService dataService) { _dataService dataService; } public void LoadCharacterConfig() { _currentCharacterConfig _dataService.GetConfigCharacterConfig(CharacterConfig); Debug.Log($逻辑层加载到角色 {_currentCharacterConfig.characterName}); // 在实际项目中这里会发布一个 CharacterConfigLoadedEvent } public string GetCharacterName() _currentCharacterConfig?.characterName; }表现层创建UI和ViewModel。// Assets/Scripts/Presentation/UI/ViewModels/PlayerInfoViewModel.cs public class PlayerInfoViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _characterName; public string CharacterName { get _characterName; set { if (_characterName ! value) { _characterName value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CharacterName))); } } } } // Assets/Scripts/Presentation/UI/Views/PlayerInfoView.cs public class PlayerInfoView : MonoBehaviour { [SerializeField] private Text _nameText; [SerializeField] private Button _loadButton; private PlayerInfoViewModel _viewModel; private PlayerManager _playerManager; // 应通过依赖注入获取此处简化 void Start() { _viewModel new PlayerInfoViewModel(); _viewModel.PropertyChanged (s, e) { if (e.PropertyName nameof(PlayerInfoViewModel.CharacterName)) _nameText.text _viewModel.CharacterName; }; _loadButton.onClick.AddListener(OnLoadButtonClick); } void OnLoadButtonClick() { // 这里演示直接调用理想情况应通过命令或中介者 _playerManager.LoadCharacterConfig(); _viewModel.CharacterName _playerManager.GetCharacterName(); } }组合根在启动场景中注册所有依赖。// Assets/Scripts/Core/GameLifetimeScope.cs public class GameLifetimeScope : LifetimeScope { protected override void Configure(IContainerBuilder builder) { builder.RegisterIDataService, DataService(Lifetime.Singleton); builder.RegisterPlayerManager(Lifetime.Singleton); // 注意这里需要将PlayerManager注入到View中可能需要使用工厂或方法注入此处略过 } }通过这个简单例子你已经看到了数据如何从配置资产经过服务层到达逻辑层最终驱动表现层更新的完整链条。虽然省略了事件总线和更复杂的依赖注入但骨架已然清晰。9. 常见问题、性能优化与避坑指南9.1 依赖注入循环依赖问题问题A依赖BB又依赖A容器无法解析。解决方案重新审视设计循环依赖通常意味着职责划分不清。考虑是否能将A和B共同依赖的功能抽离到第三个服务C中。使用接口延迟注入将其中一个依赖改为LazyT或FuncT注入在真正需要时才解析。属性注入对于某些非核心依赖可以使用属性注入而非构造函数注入但需谨慎使用因为它会隐藏依赖关系。9.2 事件滥用与内存泄漏问题大量对象订阅事件后没有取消订阅导致对象无法被垃圾回收。解决方案谁订阅谁负责取消在MonoBehaviour的OnDestroy方法中务必取消所有事件订阅。使用弱事件考虑使用弱事件模式如WeakReference来避免强引用导致的内存泄漏。定义清晰的订阅生命周期对于某些全局事件可以在逻辑层管理器的Initialize/Dispose方法中统一进行订阅和取消。9.3 异步操作与生命周期管理问题在Unity中大量使用async/await或UniTask进行异步加载但如果在操作完成前场景切换或对象销毁会导致回调错误或资源泄漏。解决方案使用CancellationToken为每个异步操作关联一个CancellationTokenSource并在MonoBehaviour的OnDestroy或场景卸载时调用Cancel()。private CancellationTokenSource _cts; void Start() { _cts new CancellationTokenSource(); LoadDataAsync(_cts.Token); } void OnDestroy() { _cts?.Cancel(); _cts?.Dispose(); } async void LoadDataAsync(CancellationToken ct) { try { await SomeAsyncOperation(ct); // 将token传递给底层异步方法 } catch (OperationCanceledException) { Debug.Log(加载被取消); } }利用UniTask的PlayerLoopTracker如果使用UniTask它可以更好地与Unity的生命周期集成。9.4 编辑器与运行时架构的协调问题架构主要针对运行时设计但编辑器下的工具开发如关卡编辑器、技能编辑器也需要共享数据和逻辑。解决方案共享数据层确保编辑器工具和游戏运行时使用同一套数据模型ScriptableObject或序列化类。服务抽象将编辑器特有的功能如场景物体拾取、网格绘制也封装成服务接口在编辑器模式下注入不同的实现。例如运行时用IResourceService加载AssetBundle编辑器下可以用一个直接返回AssetDatabase.LoadAssetAtPath的实现。使用ScriptableObject创建编辑器配置很多游戏框架如Unity的Addressables系统都利用ScriptableObject来存放编辑器配置这些配置在构建时被处理运行时则使用处理后的数据。9.5 如何应对需求变更框架的扩展性体现当策划提出“我们要给游戏加上宠物系统”时高扩展性框架的优势就显现出来了数据层在Data/Configs/下创建PetConfig.asset定义宠物属性。在Data/Models/下创建RuntimePetData.cs。服务层通常无需改动。除非宠物系统需要全新的服务如宠物AI服务则新增IPetAIService并实现。逻辑层创建PetManager它依赖IDataService获取配置管理玩家拥有的宠物列表和状态。在PlayerManager中增加对PetManager的引用或通过事件交互。发布新的事件如PetUnlockedEvent。表现层创建PetView、PetHudViewModel和相应的UI。让它们订阅逻辑层发布的新事件。你会发现整个添加过程是模块化的对原有系统的入侵极小。这正是分层架构和面向接口编程带来的核心价值。从零开始搭建这样一个框架初期确实会比直接写“面条代码”花费更多时间。但一旦项目规模超过某个临界点通常是2-3个人协作或功能模块超过10个前期在架构上的投入将会以指数级回报于开发效率、代码质量和团队协作的顺畅度。这个框架不是一成不变的教条而是一个可演进的基础。你可以根据项目的实际规模和复杂度对上述分层进行裁剪或增强。最重要的是建立起“分离关注点”和“面向接口”的思维模式这比任何具体的代码实现都更有价值。