1. 服务器数据是怎么来的(协议是什么意思)
1.1 “协议” = 服务端和客户端约定的通信格式
1
| 服务端(C++) ←——协议——→ 客户端(Lua)
|
协议就是一种约定:
- 服务端说:”我推一坨二进制数据给你,按
CsAllPlayerActivityInfo 格式解读”
- 客户端说:”好的,我用
pb.decode("com.tencent.wea.protocol.CsAllPlayerActivityInfo", 数据) 解码”
1.2 协议的两种方向
| 方向 |
名称 |
含义 |
举例 |
| C → S |
C2S(Client to Server) |
客户端发给服务端 |
“我要领奖”、”请给我活动数据” |
| S → C |
Ntf(Notify 通知) |
服务端推给客户端 |
“你的任务完成了”、”活动开始了” |
1.3 协议的数据传输过程
1 2 3 4 5 6 7 8 9 10 11 12
| 服务端 │ 原始数据: { activityId=10017, score=30, taskList=[...] } │ ↓ Protobuf 编码(压缩成二进制字节流) │ 网络传输: [0x08, 0xA9, 0x4E, 0x12, ...] ← 二进制字节流 ↓ 客户端 ActivityModel_Protocol.lua │ 接收: rawDataBytes = [0x08, 0xA9, 0x4E, 0x12, ...] │ 解码: pb.decode("com.tencent.wea.protocol.CsAllPlayerActivityInfo", rawDataBytes) │ ↓ 解码后变成 Lua 表 │ msgData = { activityId=10017, score=30, taskList={...} } ↓ 存储到 Model 中
|
源码对应(ActivityModel_Protocol.lua 第539-560行):
1 2 3 4 5 6 7 8 9 10 11 12 13
| function ActivityModel_Protocol.DecodeData(activityModel, rawDataBytes, rspPbName)
local decodeParams = pb.decode( "com.tencent.wea.protocol." .. rspPbName, rawDataBytes )
return decodeParams end
|
2. Model 读取的数据包含配置表数据吗?
答案:不包含。Model 有两套独立的数据来源。
数据来源对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| ┌──────────────────────────────────────────────────────┐ │ 活动数据的两个来源 │ ├──────────────────────────────────────────────────────┤ │ │ │ 来源1: 配置表(静态数据,导表生成) │ │ ┌─────────────────────────────────────────┐ │ │ │ ActivityMainConfigForLetsGo_common.txt │ │ │ │ ↓ 导表后变为 │ │ │ │ _MOE.Config.ActivityMainConfig │ │ │ │ 包含: activityName, activityUIDetail, │ │ │ │ tagId, jumpId, backgroundUrl, │ │ │ │ timeInfo, activityShopType, ... │ │ │ └─────────────────────────────────────────┘ │ │ 特点: 固定不变, 导表后就不会改 │ │ 读取: _MOE.Config.ActivityMainConfig:GetDataByKey(id)│ │ │ │ 来源2: 服务端推送(动态数据,运行时下发) │ │ ┌─────────────────────────────────────────┐ │ │ │ ActivityInfoUpdateNtf (服务端推送) │ │ │ │ ↓ 解码后变为 │ │ │ │ _newActivityInfoMap[activityId] │ │ │ │ 包含: 任务完成状态, 当前积分, 奖励领取进度, │ │ │ │ 红点状态, 玩法进度数据 ... │ │ │ └─────────────────────────────────────────┘ │ │ 特点: 实时变化, 玩家操作后服务端推送更新 │ │ 读取: ActivityModel:GetActivityInfo(tagId, activityId)│ │ │ └──────────────────────────────────────────────────────┘
|
源码证据
ActivityModel_DataQuery.lua 第72-81行 — 有一个函数会优先读服务端数据,找不到再回退读配置表:
1 2 3 4 5 6 7 8 9
| function ActivityModel_DataQuery.GetActInfoOrCfgByActId(activityModel, activityId) local actInfo = ActivityModel_DataQuery.GetInfoByActivityId(activityModel, activityId) if not actInfo then actInfo = _MOE.Config.ActivityMainConfig:GetDataByKey(activityId) end return actInfo end
|
但基类 UI_Activity_BaseView:InitData() 用的是另一个函数(第31行):
1 2 3 4 5 6 7 8
| function UI_Activity_BaseView:InitData() self.ActivityInfo = _MOE.Models.ActivityModel:GetActivityInfo(self.Data.tagId, self.Data.id) if self.ActivityInfo == nil then self.ActivityInfo = _MOE.Models.ActivityModel:GetActivityInfoById(self.Data.id) end end
|
两个数据源的具体内容
| 字段 |
配置表数据 |
服务端推送数据 |
activityName |
✅ “春日兑换” |
✅ 同配置表(服务端也会下发) |
activityUIDetail |
✅ “UI_ActivityTemplate_…” |
✅ |
tagId |
✅ 1 |
✅ |
activityShopType |
✅ 142 |
✅ |
backgroundUrl |
✅ “spring_bg.astc” |
✅ |
timeInfo |
✅ begin/end |
✅ |
| 任务完成状态 |
❌ |
✅ {570011=已完成, 570021=未完成} |
| 当前积分 |
❌ |
✅ score=30 |
| 奖励领取进度 |
❌ |
✅ {reward1=已领, reward2=未领} |
| 红点状态 |
❌ |
✅ is_new=true |
| 玩法进度数据 |
❌ |
✅ 各活动自定义数据 |
💡 总结:服务端推送的数据包含了配置表的静态数据 + 玩家的动态进度数据。所以 View 读到的 ActivityInfo 是两份合并后的完整数据。
3. 传入 View 的 Data 到底是什么
View 的 Data 来自两个地方,在 OnOpen 时传入:
3.1 Data 的来源
1 2 3 4 5 6 7 8 9 10 11 12 13
| UI_Events_Main.lua 第 808-819行: local activityUIDetail = v.activityUIDetail -- ← 来自服务端推送的活动数据 tabInfo.UIName = activityUIDetail tabInfo.id = v.id -- ← 活动ID tabInfo.tagId = v.tagId -- ← 标签ID
第 1081行: self.SubView = self:AddWidget(tabInfo.UIName, slot, info) -- info 就是 Data, 包含: -- info.id = 活动ID -- info.tagId = 标签ID -- info.activityUIDetail = UMG名 -- info + 其他活动配置字段
|
3.2 Data 包含的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Data = { id = 10017, tagId = 1, activityName = "春日兑换", activityType = "ATLinearRedeem", activityUIDetail = "UI_ActivityTemplate_ExchangeLinearView", activityShopType = {142}, activityTaskGroup = {570011, 570021}, backgroundUrl = {"spring_bg.astc", "spring_title.astc"}, timeInfo = { beginTime={...}, endTime={...} }, clientParams = {"每日任务", "兑换商店"}, activityParam = {5, 1300001}, platforms = {1, 2, 3, 4}, lowVersion = "1.5.61.25", suspend = 0,
}
|
3.3 Data vs ActivityInfo 的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ┌──────────────────────────────────────────────────────────┐ │ View 拿到的数据有两个 │ ├──────────────────────────────────────────────────────────┤ │ │ │ 1. self.Data ← OnOpen 时传入 │ │ 来源: UI_Events_Main 从活动列表数据中取出 │ │ 内容: 活动的基础信息 (id, tagId, activityName, ...) │ │ 用途: 知道"这是哪个活动" │ │ │ │ 2. self.ActivityInfo ← InitData 中获取 │ │ 来源: ActivityModel:GetActivityInfo(tagId, activityId)│ │ 内容: 活动的完整数据 (基础信息 + 玩家进度 + 奖励状态) │ │ 用途: 知道"活动当前是什么状态" │ │ │ └──────────────────────────────────────────────────────────┘
|
源码对应(UI_Activity_BaseView.lua 第16-36行):
1 2 3 4 5 6 7 8 9 10 11 12 13
| function UI_Activity_BaseView:Init(child, Data) self.child = child self.Data = Data self.bp = self.child.bp end
function UI_Activity_BaseView:InitData() self.ActivityInfo = _MOE.Models.ActivityModel:GetActivityInfo(self.Data.tagId, self.Data.id) end
|
4. 完整数据流图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| ┌─────────────────────────────────────────────────────────────────┐ │ 完整数据流: 从服务端到玩家看到的UI │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ │ │ │ 配置表 │ ActivityMainConfigForLetsGo_*.txt (导表后) │ │ │ (静态) │ → _MOE.Config.ActivityMainConfig │ │ └────┬─────┘ 包含: activityName, UMG名, tagId, 商城, 时间... │ │ │ │ │ │ ┌──────────┐ │ │ │ │ 服务端 │ ActivityInfoUpdateNtf (Protobuf字节流) │ │ │ │ (动态) │ → pb.decode 解码 │ │ │ └────┬─────┘ │ │ │ │ 包含: 任务状态, 积分, 奖励进度, 红点... │ │ │ │ │ │ ↓ ↓ │ │ ┌─────────────────────┐ │ │ │ Model 层 │ │ │ │ │ │ │ │ Activity_Persistent │ ← 接收服务端 Ntf (门卫) │ │ │ ↓ │ │ │ │ ActivityModel_ │ ← 解码 Protobuf (拆包裹) │ │ │ Protocol │ │ │ │ ↓ │ │ │ │ ActivityModel_ │ ← 存储 (入库) │ │ │ DataQuery │ m_ActivityInfoMap[tagId] = 活动列表 │ │ │ │ _newActivityInfoMap[id] = 动态数据 │ │ │ ↓ │ │ │ │ DispatchEvent │ ← 广播 (通知前台) │ │ │ (OnActivityInfo │ "活动10017更新了!" │ │ │ UpdateNtf, id) │ │ │ └─────────┬───────────┘ │ │ │ │ │ ↓ │ │ ┌─────────────────────┐ │ │ │ View 层 │ │ │ │ │ │ │ │ AddEventListener │ ← 听到广播 │ │ │ ↓ │ │ │ │ GetActivityInfo() │ ← 去仓库取货 │ │ │ 返回 ActivityInfo │ (配置表字段 + 服务端动态数据 合并) │ │ │ ↓ │ │ │ │ self.ActivityInfo │ ← 存到 View │ │ │ ↓ │ │ │ │ InitUI() │ ← 摆上货架 │ │ │ ├─ TextSetText │ 标题 = ActivityInfo.activityName │ │ │ ├─ ImgSetTexture │ 背景图 = ActivityInfo.backgroundUrl │ │ │ ├─ ListSetDatas │ 任务列表 = ActivityInfo.taskList │ │ │ └─ SetVisibility │ 按钮状态 = 根据任务状态判断 │ │ └─────────────────────┘ │ │ │ │ │ ↓ │ │ ┌─────────────────────┐ │ │ │ UMG 蓝图 │ │ │ │ 玩家看到的界面 │ │ │ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘
|
5. 一句话总结
| 问题 |
答案 |
| 协议是什么? |
服务端和客户端约定的通信格式,用 Protobuf 编解码 |
| Model 怎么读服务端数据? |
收到二进制字节流 → pb.decode() 解码成 Lua 表 → 存到内存 |
| Model 数据包含配置表数据吗? |
服务端推送的数据本身就包含了配置表字段(服务端也会读配置表并下发) |
| 传入 View 的 Data 是什么? |
活动的基础信息(id, tagId 等),用于”知道是哪个活动” |
| ActivityInfo 是什么? |
从 Model 读取的完整数据(配置表字段 + 玩家动态进度 + 奖励状态) |
| View 怎么显示数据? |
self.ActivityInfo.xxx → 写入 self.bp.w_xxx(UMG Widget) |
本文从 LearnByCompany 原始文档 自动同步。