510 lines
15 KiB
Markdown
510 lines
15 KiB
Markdown
# 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 仍需手动注册
|
||
|
||
**无需改动框架即可新增/删除活动**。
|