12 KiB
12 KiB
旧活动框架迁移到新框架指南
本文档指导如何将旧的活动框架(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 是活动是否显示的关键判断,其设计原则:
// 基类实现 - 只判断必要条件
public virtual bool IsActive => EventID > 0 && RemainingTime.TotalSeconds > 0;
为什么要保留 RemainingTime 判断?
- 活动过期时需要立即隐藏按钮
TimeConditionChecker只在活动时间变化时触发(非每秒检查)AbstractHomeEntranceBtn.UpdateTimer每秒调用,通过RemainingTime确保过期立即隐藏
为什么要移除 StartTime 判断?
TimeConditionChecker已在活动开启时判断过开始时间- 重复判断会增加性能开销
2.2 子类重写 IsActive
子类可以重写 IsActive 添加业务条件判断:
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/
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/
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/
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/
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 事件系统(完全兼容)
// 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 全局事件(完全兼容)
// 继续使用旧框架的全局事件,无需修改!
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 数据存储(自动兼容)
// 新框架自动使用 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 |
条件检查器列表 |
// 在 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) 组合一个实例,如需多实例需要扩展框架。