# 旧活动框架迁移到新框架指南 本文档指导如何将旧的活动框架(DataCenter + Act)迁移到新的 Activity 框架。 --- ## 一、迁移前的准备 ### 1.1 了解新框架的核心组件 | 组件 | 作用 | |------|------| | `ActivityManager` | 活动总控,管理所有活动的开启/关闭 | | `AActivitySentry` | 活动哨兵基类,负责活动的生命周期(开启/关闭)及数据管理器解析 | | `AEventDataManager` | 活动数据管理器,负责数据的加载/保存 | | `AEventData` | 活动数据类,定义活动运行时数据结构 | | `AbstractHomeEntranceBtn` | 主页入口按钮,自动管理活动入口 | ### 1.2 旧框架与新框架对照 | 旧框架 | 新框架 | 兼容情况 | |--------|--------|---------| | `EventPotionDataManager` | `AEventDataManager` | ✅ 已兼容 | | `EventPotionData` | `AEventData` | ✅ 需迁移 | | `FishingPotionAct : AGameAct` | `AActivitySentry` | ✅ 需迁移 | | `eventAggregator.Publish()` | `IEventAggregator` | ✅ **完全兼容** | | `GameEventMgr` | 保留共存 | ✅ **完全兼容** | | `PlayFabMgr` 数据存储 | `EventDataRepository` | ✅ **已内置** | --- ## 二、IsActive 设计说明 ### 2.1 设计原理 `AEventData.IsActive` 是活动是否显示的关键判断,其设计原则: ```csharp // 基类实现 - 只判断必要条件 public virtual bool IsActive => EventID > 0 && RemainingTime.TotalSeconds > 0; ``` **为什么要保留 RemainingTime 判断?** - 活动过期时需要**立即隐藏**按钮 - `TimeConditionChecker` 只在活动时间变化时触发(非每秒检查) - `AbstractHomeEntranceBtn.UpdateTimer` 每秒调用,通过 `RemainingTime` 确保过期立即隐藏 **为什么要移除 StartTime 判断?** - `TimeConditionChecker` 已在活动开启时判断过开始时间 - 重复判断会增加性能开销 ### 2.2 子类重写 IsActive 子类可以重写 `IsActive` 添加业务条件判断: ```csharp public class CapsulePackData : AEventData { public int CurrentRoundCount; // 重写 IsActive 添加业务条件 public override bool IsActive { get { // 先调用基类判断活动时间 if (!base.IsActive) return false; // 添加业务条件:回合数未满 var maxCount = MainConfig.PackManagerID?.Count ?? 0; return CurrentRoundCount < maxCount; } } } ``` **注意**: - 始终先调用 `base.IsActive` 检查活动时间 - 业务条件判断应简洁,避免复杂计算 --- ## 三、迁移步骤 ### Step 1: 创建活动数据类 (AEventData) **位置**: `Activity/Data/` ```csharp using Newtonsoft.Json; namespace Activity.Data { /// /// 酿造活动数据 /// 对应旧框架的 EventPotionData /// public class EventPotionData : AEventData { public int PotionID; public int CurrentLevel; public List LevelProcessedList = new(); public int Score; public int Token; // 可添加旧框架的其他字段... } } ``` ### Step 2: 创建活动数据管理器 (AEventDataManager) **位置**: `Activity/Data/` 或 `DataCenter/` ```csharp using Activity.Data; using asap.core; using cfg; using GameCore; using UniRx; using UnityEngine; namespace game { /// /// 酿造活动数据管理器 /// 继承 AEventDataManager,自动管理数据加载/保存 /// public class EventPotionDataManager : AEventDataManager { #region 旧框架迁移的方法 /// /// 对应旧框架的 InitLevel() /// protected override void OnDataInitialized(EventPotionData data, FishingEvent eventConfig) { // 自动获取膨胀率 data.InflationRate = GContext.container.Resolve().InflationRate; // 旧框架 InitLevel() 逻辑可以搬到这里 if (data.LevelProcessedList == null || data.LevelProcessedList.Count == 0) { data.LevelProcessedList = new List(); } } /// /// 对应旧框架的 FixPotionData() /// public void FixData(EventPotionData data) { if (data == null) return; // 数据修复逻辑从旧框架搬过来 if (data.PotionID <= 0) { data.PotionID = 1; } } /// /// 获取配置表数据 - 旧框架的方法保留 /// public EventPotionMain GetEventPotionMain(int id) { return Tables.TbEventPotionMain.GetOrDefault(id); } public EventPotionStage GetEventPotionStage(int id) { return Tables.TbEventPotionStage.GetOrDefault(id); } #endregion } } ``` ### Step 3: 创建活动哨兵 (AActivitySentry) **位置**: `Activity/Sentry/` ```csharp using Activity.Condition; using Activity.HomeEntranceBtn; using cfg; using game; namespace Activity.Sentry { /// /// 酿造活动哨兵 /// 对应旧框架的 FishingPotionAct /// [ActivitySentry(eventType: 12, eventSubType: 1)] // 对应配置表中的 Type 和 SubType public class EventPotionSentry : AActivitySentry { /// /// 主页入口按钮实例 /// public AbstractHomeEntranceBtn HomeEntranceBtn { get; set; } #region 生命周期钩子 /// /// 活动开启时调用 /// 对应旧框架 FishingPotionAct.StartAsync() /// protected override void OnEventActive(FishingEvent fishingEvent) { // 1. 获取或创建活动数据(自动从 PlayFab 加载) var manager = ResolveManager(); var data = manager.RefreshData(fishingEvent); // 2. 数据修复(对应旧框架的 FixPotionData) manager.FixData(data); // 3. 通知按钮活动已开启(自动预加载资源、显示入口) HomeEntranceBtn?.OnOpen(fishingEvent, manager); Debug.Log($"[EventPotionSentry] Activity opened: {fishingEvent.ID}"); } /// /// 活动关闭时调用 /// 对应旧框架 FishingPotionAct.StopAsync() /// protected override void OnEventClosed(FishingEvent fishingEvent) { // 数据会自动保存(通过 EventDataRepository) // 通知按钮活动关闭 HomeEntranceBtn?.OnClose(fishingEvent); Debug.Log($"[EventPotionSentry] Activity closed: {fishingEvent.ID}"); } /// /// 活动切换时调用(可选) /// protected override void OnEventChanged(FishingEvent previousEventConfig, FishingEvent newEventConfig) { Debug.Log($"[EventPotionSentry] Activity changed: {previousEventConfig?.ID} -> {newEventConfig.ID}"); } #endregion } } ``` ### Step 4: 创建主页入口按钮(可选但推荐) **位置**: `Activity/HomeEntranceBtn/` ```csharp using Activity.Data; using game; using UnityEngine; namespace Activity.HomeEntranceBtn { /// /// 酿造活动入口按钮 /// 自动化管理活动入口的显示、预加载、倒计时 /// public class EventPotionEntranceBtn : AbstractHomeEntranceBtn { #region Abstract Implementation /// /// 关联的 Sentry 类型 /// protected override System.Type AssociatedSentryType => typeof(EventPotionSentry); /// /// 按钮点击事件 /// protected override void OnClickBtn() { // 打开活动面板 - 和旧框架一样 UIManager.Instance.ShowUINotLoading(UITypes.EventPotionPanel); } /// /// 图标资源名称 /// protected override string GetIconName() => "icon_potion"; /// /// 需要预加载的资源列表 /// protected override System.Collections.Generic.List GetResourceNames() { return new System.Collections.Generic.List { "Prefab_EventPotionPanel", "Atlas_Potion" }; } #endregion } } ``` --- ## 四、保留旧框架功能 ### 4.1 eventAggregator 事件系统(完全兼容) ```csharp // UI 层继续使用原来的方式,无需修改! public class EventPotionPanel : MonoBehaviour { private IEventAggregator _eventAggregator = new EventAggregator(); private void OnClick() { // 和旧框架完全一样 _eventAggregator.Publish(new HideHomePanelEvent()); // 或者获取事件 _eventAggregator.GetEvent().Subscribe(OnEvent); } } ``` ### 4.2 GameEventMgr 全局事件(完全兼容) ```csharp // 继续使用旧框架的全局事件,无需修改! public class EventPotionPanel : MonoBehaviour { private void OnEnable() { GameEventMgr.Instance.AddListener(MyEvents.LEVEL_COMPLETE, OnLevelComplete); } private void OnDisable() { GameEventMgr.Instance.RemoveListener(MyEvents.LEVEL_COMPLETE, OnLevelComplete); } private void OnLevelComplete(int level) { // 处理事件 } } ``` ### 4.3 数据存储(自动兼容) ```csharp // 新框架自动使用 PlayFabMgr 保存数据,无需手动调用 SyncData! public class EventPotionDataManager : AEventDataManager { public void AddScore(int score) { var data = LoadData(); data.Score += score; // 自动保存到 PlayFab(通过 EventDataRepository) SaveData(data); } } ``` --- ## 五、配置表映射 旧框架的活动配置通常在 `FishingEvent` 配置表中定义,迁移时需要确保: | 配置字段 | 说明 | |----------|------| | `Type` | 活动类型,对应 `ActivitySentryAttribute` 的 `eventType` | | `SubType` | 活动子类型,对应 `ActivitySentryAttribute` 的 `eventSubType` | | `TimeDefinition` | 时间定义,用于 `TimeConditionChecker` | | `ConditionCheckers` | 条件检查器列表 | ```csharp // 在 FishingEvent 配置表中添加条件检查器 // 新框架会自动读取并执行条件检查 ``` --- ## 六、迁移检查清单 - [ ] 创建活动数据类 `AEventData` - [ ] 创建活动数据管理器 `AEventDataManager` - [ ] 迁移数据初始化逻辑(InitLevel → OnDataInitialized) - [ ] 迁移数据修复逻辑(FixPotionData → FixData) - [ ] 创建活动哨兵 `AActivitySentry` - [ ] 标注特性 `[ActivitySentry(eventType: X, eventSubType: Y)]` - [ ] 迁移活动入口逻辑(StartAsync → OnEventActive) - [ ] 创建主页入口按钮(可选) - [ ] 保留 eventAggregator 用法(UI层代码不变) - [ ] 保留 GameEventMgr 用法(代码不变) - [ ] 删除旧框架的 Act 类 --- ## 七、常见问题 ### Q1: 旧框架的 Act 有复杂的资源加载逻辑怎么办? **A**: 在 `OnEventActive` 中保留资源加载逻辑,或在 `HomeEntranceBtn.GetResourceNames()` 中配置预加载资源。 ### Q2: 活动需要网络请求怎么办? **A**: 新框架暂未内置网络层,建议在 UI 层调用原有的 RTService 或在 Sentry 中添加网络逻辑。 ### Q3: 多个同类型活动如何处理? **A**: 当前 Sentry 设计为每个 (Type, SubType) 组合一个实例,如需多实例需要扩展框架。 --- ## 八、相关文档 - [Activity 框架 README](./README.md) - [条件检查器使用指南](./Condition/) - [事件订阅器使用指南](./EventSubscriber/)