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

15 KiB
Raw Permalink Blame History

Activity 活动框架

活动管理系统,负责监听事件、判断条件、调用 Sentry 执行开关。

架构概览

Activity/
├── ActivityManager.cs              # 核心管理器 - 协调整个活动系统
├── ActivityScopeManager.cs          # DI Scope 管理器 - 每个活动独立的依赖注入容器
├── Condition/                       # 条件检查模块
│   ├── IConditionChecker.cs        # 条件检查器接口
│   ├── ConditionCheckerFactory.cs   # 条件检查器工厂
│   ├── TimeConditionChecker.cs       # 时间条件检查器
│   ├── AccountLevelConditionChecker.cs  # 账号等级条件检查器
│   ├── FishCountConditionChecker.cs    # 捕鱼数量条件检查器
│   └── ABTestConditionChecker.cs    # AB测试条件检查器
├── Data/                            # 数据层
│   ├── IEventData.cs                # 活动数据接口
│   ├── AEventData.cs                # 活动数据基类
│   ├── AEventDataManager.cs         # 活动数据管理器基类
│   ├── IEventDataRepository.cs      # 数据仓库接口
│   └── EventDataRepository.cs       # 数据仓库实现
├── Sentry/                          # 活动哨兵模块
│   ├── IActivitySentry.cs           # 哨兵接口
│   ├── AActivitySentry.cs           # 哨兵基类AActivitySentry<TM, TD>
│   ├── ActivitySentryManager.cs     # 哨兵管理器
│   └── ActivitySentryAttribute.cs   # 哨兵特性
├── EventSubscriber/                 # 事件订阅模块
│   ├── IActivityEventSubscriber.cs  # 事件订阅器接口
│   ├── ActivitySubscriberManager.cs # 订阅器管理器
│   └── *.cs                         # 具体订阅器实现
├── HomeEntranceBtn/                 # 主页入口按钮
│   ├── IHomeEntranceBtn.cs          # 按钮接口
│   └── AbstractHomeEntranceBtn.cs   # 按钮基类
├── Event/                           # 事件定义
│   └── GMActivityOpenedEvent.cs     # GM开启活动事件
└── Services/                        # 服务层
    ├── IEventTokenService.cs        # 代币服务接口
    └── EventTokenService.cs         # 代币服务实现

核心概念

1. 条件检查器 (IConditionChecker)

负责判断活动是否应该开启。框架支持多种内置条件检查器:

  • TimeConditionChecker - 时间条件(活动开放时间段)
  • AccountLevelConditionChecker - 账号等级条件
  • FishCountConditionChecker - 捕鱼数量条件
  • ABTestConditionChecker - AB测试分组条件

每个条件检查器实现 IConditionChecker 接口:

public interface IConditionChecker
{
    bool CanOpen(FishingEvent fishingEvent);           // 判断是否能开启
    int Priority { get; }                              // 优先级
    List<Type> CareDataChangeWithType { get; }        // 关心的数据类型变化
}

2. 活动哨兵 (IActivitySentry)

活动哨兵负责执行活动的开启和关闭操作。每个活动类型对应一个 Sentry 实例。

[ActivitySentry(eventType: 1, eventSubType: 1)]
public class MyActivitySentry : AActivitySentry<MyEventDataManager, MyEventData>
{
    /// <summary>
    /// 主页入口按钮实例
    /// </summary>
    public IHomeEntranceBtn HomeEntranceBtn { get; set; }

    protected override void OnEventActive(FishingEvent fishingEvent)
    {
        // 1. 获取或创建活动数据(自动从 PlayFab 加载)
        var manager = ResolveManager();
        var data = manager.RefreshData<MyEventData>(fishingEvent);

        // 2. 通知按钮活动已开启(自动预加载资源、显示入口)
        HomeEntranceBtn?.OnOpen(fishingEvent, manager);
    }

    protected override void OnEventClosed(FishingEvent fishingEvent)
    {
        // 数据会自动保存(通过 EventDataRepository

        // 通知按钮活动关闭
        HomeEntranceBtn?.OnClose(fishingEvent);
    }

    protected override void OnEventChanged(FishingEvent previousEventConfig, FishingEvent newEventConfig)
    {
        // 同一 (Type, SubType) 下切换到新的配置时触发
        // 框架不会对 previousEventConfig 额外调用 OnEventClosed只会
        // 1. 更新 CurrentEventConfig
        // 2. 调用 OnEventChanged(previous, new)
        // 3. 调用 OnEventActive(new)
    }
}

3. 活动数据 (IEventData)

活动运行时数据包含活动ID、膨胀率、活动状态等

public class MyEventData : AEventData
{
    // 自定义数据字段
}

4. 数据管理器 (AEventDataManager)

管理活动数据的加载、保存、刷新:

public class MyEventDataManager : AEventDataManager<MyEventData>
{
    protected override void OnDataInitialized(MyEventData data, FishingEvent eventConfig)
    {
        // 数据初始化逻辑
    }
}

5. 主页入口按钮 (AbstractHomeEntranceBtn)

活动在主页的入口按钮框架,提供活动入口的通用功能:

public class MyActivityEntranceBtn : AbstractHomeEntranceBtn<MyEventData>
{
    #region Abstract Implementation

    /// <summary>
    /// 关联的 Sentry 类型,用于注册按钮到哨兵
    /// </summary>
    protected override Type AssociatedSentryType => typeof(MyActivitySentry);

    /// <summary>
    /// 按钮点击事件处理
    /// </summary>
    protected override void OnClickBtn()
    {
        // 打开活动面板
    }

    /// <summary>
    /// 获取图标资源名称
    /// </summary>
    protected override string GetIconName() => "icon_my_activity";

    /// <summary>
    /// 获取需要预加载的资源列表
    /// </summary>
    protected override List<string> GetResourceNames() => new() { "Prefab_MyActivity" };

    #endregion
}

核心功能

功能 说明
泛型类型 TData 活动数据类型
Sentry 集成 自动注册/注销按钮到对应的 ActivitySentry
定时器 自动每秒更新活动倒计时显示
资源预加载 活动开启前预加载所需资源
生命周期 继承 EventButtonResource支持完整的创建/销毁流程

UI 组件绑定

框架会自动查找并绑定以下子物体:

字段 默认路径 说明
textTimer text_time 倒计时文本
btn 自身 按钮组件
icon icon_task 图标图片

⚠️ 注意: 确保预制体中包含对应名称的子物体,否则需要手动赋值或重写绑定逻辑

6. 事件订阅器 (IActivityEventSubscriber)

订阅游戏内事件,触发活动状态检查:

public class MyActivitySubscriber : IActivityEventSubscriber
{
    public void Subscribe(ActivityManager manager)
    {
        // 订阅游戏事件
    }

    public void Unsubscribe()
    {
        // 取消订阅
    }
}

工作流程

初始化流程

ActivityManager.Init()
  ├── ConditionCheckerFactory.Initialize()     // 初始化条件检查器工厂
  ├── InitAllManage()                           // 注册 EventDataRepository、扫描 Sentry 和 Subscriber
  ├── InitializeAllSentries()                  // 为每个活动类型创建 Sentry 实例
  ├── SubscribeGlobalEvents()                   // 订阅全局事件
  └── CheckAllActivities()                      // 检查所有活动状态

活动状态检查流程

数据变化 或 定时检查
    │
    ▼
ActivityManager.OnDataChanged<T>(data)
    │
    ├── 根据数据类型找到相关活动
    │
    ├── 遍历活动调用 ShouldActivityOpen()
    │       │
    │       └── 遍历所有条件检查器 (IConditionChecker.CanOpen)
    │
    └── 根据结果调用 Sentry.Open() 或 Sentry.Close()

活动开关执行流程

Sentry.Open(fishingEvent)
    │
    ├── 设置 CurrentEventConfig
    │
    ├── 调用 OnEventChanged() (活动切换时)
    │
    ├── 调用 OnEventActive()
    │       │
    │       └── 通知 HomeEntranceBtn.OnOpen()
    │               │
    │               └── 预加载资源 -> OnLoadEventResource()
    │
    └── 创建/刷新活动数据

按钮注册流程

Awake()
    │
    ├── RegisterBtnInSentry()   // 注册到 Sentry
    │       │
    │       └── 获取 TypeKey 从 AssociatedSentryType
    │       │
    │       └── 设置 sentry.HomeEntranceBtn = this
    │
    ├── 查找并绑定 UI 组件
    │
    ├── 设置按钮点击监听
    │
    └── 检查活动状态 -> OnOpen() 或 等待

使用示例

1. 创建新的活动

  1. 定义活动数据类
public class MyEventData : AEventData
{
    public int TokenCount;
    public List<int> RewardsClaimed;
}
  1. 定义数据管理器
public class MyEventDataManager : AEventDataManager<MyEventData>
{
    protected override void OnDataInitialized(MyEventData data, FishingEvent eventConfig)
    {
        data.InflationRate = GContext.container.Resolve<PlayerData>().InflationRate;
    }
}
  1. 定义活动哨兵
[ActivitySentry(eventType: 1, eventSubType: 1)]
public class MyActivitySentry : AActivitySentry
{
    /// <summary>
    /// 主页入口按钮实例
    /// </summary>
    public AbstractHomeEntranceBtn HomeEntranceBtn { get; set; }

    protected override void OnEventActive(FishingEvent fishingEvent)
    {
        // 创建活动数据
        var manager = ResolveManager<MyEventDataManager>((EventType, EventSubType));
        var data = manager.RefreshData(fishingEvent);

        // 通知按钮活动已开启
        HomeEntranceBtn?.OnOpen();
    }

    protected override void OnEventClosed(FishingEvent fishingEvent)
    {
        // 保存数据
        HomeEntranceBtn?.OnClose();
    }
}
  1. 定义主页入口按钮
public class MyActivityEntranceBtn : AbstractHomeEntranceBtn<MyEventData, MyEventDataManager>
{
    protected override Type AssociatedSentryType => typeof(MyActivitySentry);

    protected override void OnClickBtn()
    {
        // 打开活动面板
    }

    protected override string GetIconName() => "icon_my_activity";

    protected override List<string> GetResourceNames() => new() { "Prefab_MyActivity" };
}

2. 添加新的条件检查器

public class MyConditionChecker : IConditionChecker
{
    public int Priority => 10;

    public List<Type> CareDataChangeWithType => new()
    {
        typeof(PlayerLevelData)
    };

    public bool CanOpen(FishingEvent fishingEvent)
    {
        var playerLevel = GContext.container.Resolve<PlayerData>().Level;
        return playerLevel >= fishingEvent.ConditionParam;
    }
}

然后在 ConditionCheckerFactory.Initialize() 中注册。

3. 添加事件订阅器

public class MyActivityEventSubscriber : IActivityEventSubscriber
{
    private IDisposable _subscription;

    public void Subscribe(ActivityManager manager)
    {
        // 订阅游戏内事件
        _subscription = MessageBroker.Default.Receive<SomeGameEvent>()
            .Subscribe(_ => manager.OnActivitySingleEvent(...));
    }

    public void Unsubscribe()
    {
        _subscription?.Dispose();
    }
}

注册机制

ActivityScopeManager 统一管理

注册逻辑统一放在 ActivityScopeManager 中,避免重复代码:

方法 说明
IsRegistered<TM>() 检查类型是否已注册
GetOrRegisterInstance<TM>(typeKey) 获取或注册实例(自动创建 Scope
GetRegisteredTypeKey<TM>() 获取已注册类型对应的 typeKey
// 在 AActivitySentry 中获取管理器实例
protected TM ResolveManager()
{
    return _scopeManager.GetOrRegisterInstance<TM>((EventType, EventSubType));
}

// 在 ActivityManager 中解析
public TM Resolve<TM>() where TM : AEventDataManager
{
    if (!_scopeManager.IsRegistered<TM>()) return null;
    var typeKey = _scopeManager.GetRegisteredTypeKey<TM>();
    if (typeKey == null) return null;
    if (!_scopeManager.HasActiveScope(typeKey.Value)) return null;
    return _scopeManager.GetOrRegisterInstance<TM>(typeKey.Value);
}

注册流程

ResolveManager() 调用
    │
    ├── GetOrRegisterInstance(typeKey)
    │       │
    │       ├── 获取或创建 Scope (GetActivityScope)
    │       │
    │       ├── 如果未注册:
    │       │       │
    │       │       ├── GContext.container.Register<TM>().PerScope()
    │       │       │
    │       │       └── _activeRegistry.Add(typeof(TM), typeKey)
    │       │
    │       └── 返回 scope.GetInstance((typeof(TM), typeof(TM).Name))
    │
    └── 返回 TM 实例

配置表

活动配置使用 FishingEvent 配置表,包含:

  • Type - 活动类型
  • SubType - 活动子类型
  • TimeDefinition - 时间定义
  • ConditionCheckers - 条件检查器列表

注意事项

  1. Scope 隔离: 每个 (Type, SubType) 组合对应一个独立的 DI Scope
  2. 数据持久化: 通过 IEventDataRepository 进行数据存储
  3. 代币管理: 通过 IEventTokenService 统一管理活动代币
  4. 条件检查: 支持多条件组合,全部通过才能开启活动
  5. 生命周期: Sentry 实现了 IDisposable,确保资源正确释放
  6. 按钮预制体: 确保 UI 组件路径正确,或手动在 Inspector 中赋值
  7. Sentry 注册: 子类必须正确实现 AssociatedSentryType,否则按钮无法注册
  8. 统一注册: 所有管理器实例通过 ActivityScopeManager 统一注册和管理

可插拔性评估

什么是可插拔?

可插拔 = 无需修改框架核心代码,即可新增或删除活动。

当前框架可插拔性

组件 可插拔? 机制
Sentry [ActivitySentry] 属性自动发现
DataManager 通过 Sentry 泛型参数自动关联
HomeEntranceBtn 继承 AbstractHomeEntranceBtn 即可
ConditionChecker 需手动添加到 ConditionCheckerFactory
EventSubscriber 需手动注册到 ActivitySubscriberManager

新增活动的影响

以 CapsulePack扭蛋为例

需创建的文件 框架修改?
CapsulePackSentry.cs 通过 [ActivitySentry] 自动发现
CapsulePackManager.cs 通过 Sentry 泛型自动关联
IceCapsuleEntranceButton.cs 继承基类即可
CapsulePackPanel.cs 业务代码
Excel 配置表 正常配置

结论:新增活动不需要修改框架代码,只需添加业务文件。

删除活动的影响

删除内容 影响
Sentry 删除文件即失效
DataManager 删除文件即失效
HomeEntranceBtn 移除预制件即可
配置表 删除 Excel 即可

结论:删除活动影响最小,框架自动处理。

结论

框架可插拔性较好

  • 新增/删除 Sentry、DataManager、HomeEntranceBtn 完全可插拔
  • UI Panel 取决于具体实现方式
  • ConditionChecker 和 EventSubscriber 仍需手动注册

无需改动框架即可新增/删除活动