Files
ft/Client/Assets/Scripts/Activity/MIGRATION_GUIDE.md
2026-06-29 21:18:33 +08:00

12 KiB
Raw Permalink Blame History

旧活动框架迁移到新框架指南

本文档指导如何将旧的活动框架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 活动类型,对应 ActivitySentryAttributeeventType
SubType 活动子类型,对应 ActivitySentryAttributeeventSubType
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) 组合一个实例,如需多实例需要扩展框架。


八、相关文档