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

510 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 接口:
```csharp
public interface IConditionChecker
{
bool CanOpen(FishingEvent fishingEvent); // 判断是否能开启
int Priority { get; } // 优先级
List<Type> CareDataChangeWithType { get; } // 关心的数据类型变化
}
```
### 2. 活动哨兵 (IActivitySentry)
活动哨兵负责执行活动的开启和关闭操作。每个活动类型对应一个 Sentry 实例。
```csharp
[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、膨胀率、活动状态等
```csharp
public class MyEventData : AEventData
{
// 自定义数据字段
}
```
### 4. 数据管理器 (AEventDataManager)
管理活动数据的加载、保存、刷新:
```csharp
public class MyEventDataManager : AEventDataManager<MyEventData>
{
protected override void OnDataInitialized(MyEventData data, FishingEvent eventConfig)
{
// 数据初始化逻辑
}
}
```
### 5. 主页入口按钮 (AbstractHomeEntranceBtn)
活动在主页的入口按钮框架,提供活动入口的通用功能:
```csharp
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)
订阅游戏内事件,触发活动状态检查:
```csharp
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. **定义活动数据类**
```csharp
public class MyEventData : AEventData
{
public int TokenCount;
public List<int> RewardsClaimed;
}
```
2. **定义数据管理器**
```csharp
public class MyEventDataManager : AEventDataManager<MyEventData>
{
protected override void OnDataInitialized(MyEventData data, FishingEvent eventConfig)
{
data.InflationRate = GContext.container.Resolve<PlayerData>().InflationRate;
}
}
```
3. **定义活动哨兵**
```csharp
[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();
}
}
```
4. **定义主页入口按钮**
```csharp
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. 添加新的条件检查器
```csharp
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. 添加事件订阅器
```csharp
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 |
```csharp
// 在 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 仍需手动注册
**无需改动框架即可新增/删除活动**