428 lines
12 KiB
Markdown
428 lines
12 KiB
Markdown
# 旧活动框架迁移到新框架指南
|
||
|
||
本文档指导如何将旧的活动框架(DataCenter + Act)迁移到新的 Activity 框架。
|
||
|
||
---
|
||
|
||
## 一、迁移前的准备
|
||
|
||
### 1.1 了解新框架的核心组件
|
||
|
||
| 组件 | 作用 |
|
||
|------|------|
|
||
| `ActivityManager` | 活动总控,管理所有活动的开启/关闭 |
|
||
| `AActivitySentry<TM, TD>` | 活动哨兵基类,负责活动的生命周期(开启/关闭)及数据管理器解析 |
|
||
| `AEventDataManager<T>` | 活动数据管理器,负责数据的加载/保存 |
|
||
| `AEventData` | 活动数据类,定义活动运行时数据结构 |
|
||
| `AbstractHomeEntranceBtn` | 主页入口按钮,自动管理活动入口 |
|
||
|
||
### 1.2 旧框架与新框架对照
|
||
|
||
| 旧框架 | 新框架 | 兼容情况 |
|
||
|--------|--------|---------|
|
||
| `EventPotionDataManager` | `AEventDataManager<T>` | ✅ 已兼容 |
|
||
| `EventPotionData` | `AEventData` | ✅ 需迁移 |
|
||
| `FishingPotionAct : AGameAct` | `AActivitySentry<TM, TD>` | ✅ 需迁移 |
|
||
| `eventAggregator.Publish<T>()` | `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
|
||
{
|
||
/// <summary>
|
||
/// 酿造活动数据
|
||
/// 对应旧框架的 EventPotionData
|
||
/// </summary>
|
||
public class EventPotionData : AEventData
|
||
{
|
||
public int PotionID;
|
||
public int CurrentLevel;
|
||
public List<LevelProcessedList> 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
|
||
{
|
||
/// <summary>
|
||
/// 酿造活动数据管理器
|
||
/// 继承 AEventDataManager,自动管理数据加载/保存
|
||
/// </summary>
|
||
public class EventPotionDataManager : AEventDataManager<EventPotionData>
|
||
{
|
||
#region 旧框架迁移的方法
|
||
|
||
/// <summary>
|
||
/// 对应旧框架的 InitLevel()
|
||
/// </summary>
|
||
protected override void OnDataInitialized(EventPotionData data, FishingEvent eventConfig)
|
||
{
|
||
// 自动获取膨胀率
|
||
data.InflationRate = GContext.container.Resolve<PlayerData>().InflationRate;
|
||
|
||
// 旧框架 InitLevel() 逻辑可以搬到这里
|
||
if (data.LevelProcessedList == null || data.LevelProcessedList.Count == 0)
|
||
{
|
||
data.LevelProcessedList = new List<LevelProcessedList>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对应旧框架的 FixPotionData()
|
||
/// </summary>
|
||
public void FixData(EventPotionData data)
|
||
{
|
||
if (data == null) return;
|
||
|
||
// 数据修复逻辑从旧框架搬过来
|
||
if (data.PotionID <= 0)
|
||
{
|
||
data.PotionID = 1;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取配置表数据 - 旧框架的方法保留
|
||
/// </summary>
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 酿造活动哨兵
|
||
/// 对应旧框架的 FishingPotionAct
|
||
/// </summary>
|
||
[ActivitySentry(eventType: 12, eventSubType: 1)] // 对应配置表中的 Type 和 SubType
|
||
public class EventPotionSentry : AActivitySentry<EventPotionDataManager, EventPotionData>
|
||
{
|
||
/// <summary>
|
||
/// 主页入口按钮实例
|
||
/// </summary>
|
||
public AbstractHomeEntranceBtn HomeEntranceBtn { get; set; }
|
||
|
||
#region 生命周期钩子
|
||
|
||
/// <summary>
|
||
/// 活动开启时调用
|
||
/// 对应旧框架 FishingPotionAct.StartAsync()
|
||
/// </summary>
|
||
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}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 活动关闭时调用
|
||
/// 对应旧框架 FishingPotionAct.StopAsync()
|
||
/// </summary>
|
||
protected override void OnEventClosed(FishingEvent fishingEvent)
|
||
{
|
||
// 数据会自动保存(通过 EventDataRepository)
|
||
|
||
// 通知按钮活动关闭
|
||
HomeEntranceBtn?.OnClose(fishingEvent);
|
||
|
||
Debug.Log($"[EventPotionSentry] Activity closed: {fishingEvent.ID}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 活动切换时调用(可选)
|
||
/// </summary>
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 酿造活动入口按钮
|
||
/// 自动化管理活动入口的显示、预加载、倒计时
|
||
/// </summary>
|
||
public class EventPotionEntranceBtn : AbstractHomeEntranceBtn<EventPotionData, EventPotionDataManager>
|
||
{
|
||
#region Abstract Implementation
|
||
|
||
/// <summary>
|
||
/// 关联的 Sentry 类型
|
||
/// </summary>
|
||
protected override System.Type AssociatedSentryType => typeof(EventPotionSentry);
|
||
|
||
/// <summary>
|
||
/// 按钮点击事件
|
||
/// </summary>
|
||
protected override void OnClickBtn()
|
||
{
|
||
// 打开活动面板 - 和旧框架一样
|
||
UIManager.Instance.ShowUINotLoading(UITypes.EventPotionPanel);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 图标资源名称
|
||
/// </summary>
|
||
protected override string GetIconName() => "icon_potion";
|
||
|
||
/// <summary>
|
||
/// 需要预加载的资源列表
|
||
/// </summary>
|
||
protected override System.Collections.Generic.List<string> GetResourceNames()
|
||
{
|
||
return new System.Collections.Generic.List<string>
|
||
{
|
||
"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<MyEvent>().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<EventPotionData>
|
||
{
|
||
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<T>`
|
||
- [ ] 迁移数据初始化逻辑(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/)
|