好的,继续输出线性兑换换皮活动的详细操作步骤。基于模板源码分析,以下是每个步骤的具体操作:


线性兑换换皮活动 — 详细操作手册

Step 1: 配置表配置

1.1 打开 Excel 配置文件

文件路径:

1
D:\LetsGoEditor\Editor\letsgo_common\excel\xls\H_活动中心配置.xlsx

操作 Sheet:根据业务选择对应 sheet,对应导出为以下 txt 之一:

  • ActivityMainConfigForLetsGo_common.txt — 通用活动
  • ActivityMainConfigForLetsGo_modyunying.txt — 特色玩法运营
  • ActivityMainConfigForLetsGo_design.txt — 策划专用

1.2 新增活动行

在对应 sheet 末尾新增一行,填写以下字段:

字段名 填写值 说明
id 30999 服务端分配的活动ID(必须唯一)
activityType ATLinearRedeem 线性兑换枚举值
activityName 春日兑换 活动显示名
activitySubName 春日好礼 子标题(可空)
activityUIDetail UI_ActivityTemplate_ExchangeLinearView ★ 核心:指向线性兑换模板视图名
activityNameType ANTLinearRedeem 名称类型枚举
tagId 1 标签组ID(决定在活动中心哪个Tab下展示)
activityShopType 142 关联兑换商城的 mallId
activityTaskGroup 570011,570021 任务组ID列表(逗号分隔,对应不同Tab的任务分组)
backgroundUrl spring_exchange_bg.astc 背景图CDN地址
timeInfo.beginTime 2026-07-20 00:00:00 活动开始时间
timeInfo.endTime 2026-08-20 23:59:59 活动结束时间
suspend 0 是否暂停(0=正常)
platforms 1,2,3,4 支持平台(1=iOS, 2=Android, 3=PC, 4=H5)
lowVersion 1.5.61.25 最低客户端版本
showInCenter true 是否在活动中心居中展示
slapFace false 是否拍脸弹窗

1.3 配置 clientParams(多Tab页签名)

在 clientParams 字段中填写 Tab 名称列表(JSON数组格式):

1
clientParams: ["春日任务", "兑换商店"]

说明:

  • 模板代码第 68 行:local tabNames = self.Data.clientParams or {}
  • 每个元素对应一个 Tab,第一个通常是兑换商店Tab(由 ExchangeTabIndex 控制,默认=1)
  • 后续Tab对应 activityTaskGroup 中的任务组

1.4 配置 activityParam(追赶机制参数,可选)

如果活动需要”消耗道具追赶到当前进度”功能:

1
activityParam: [5, 1300001]
  • activityParam[1] = 5(追赶倍数)
  • activityParam[2] = 1300001(消耗的道具ID,需在背包表中存在)

模板代码第 218 行读取:

1
local CoinId = self.ActivityInfo.activityParam and self.ActivityInfo.activityParam[2]

1.5 导表

完成 Excel 编辑后,执行导表:

1
2
3
# 使用项目导表工具
cd D:\LetsGoEditor\Editor\letsgo_common
python export_table.py --table H_活动中心配置

导出后检查 server/data/ActivityMainConfigForLetsGo_common.txt 中是否包含新增的 id: 30999 行。

导表后的数据服务器也会读取,然后服务端把这些配置表字段和玩家的动态数据打包在一起,通过协议推送给客户端


Step 2: 创建 UMG 蓝图

2.1 创建 WidgetBlueprint

在 UE4 编辑器中:

  1. Content Browser 中右键 → User Interface → Widget Blueprint
  2. 命名:UI_ActivityTemplate_ExchangeLinearView(如果美术想用独立名字,如 UI_ActivityInstance_SpringExchange,需在配置表和 UIWndNameToLuaPath.lua 中同步修改)
  3. 保存路径:/Game/Feature/System/Assets/CommercialRes/Activity/Activity_SpringExchange/Blueprints/

2.2 设置蓝图父类

  1. 打开蓝图 → 点击 Class Settings(类设置)
  2. Parent Class:选择 MoeLuaWidget(或 UMoeLuaUserWidget)

2.3 设置 View 属性(★ 关键)

  1. 在蓝图编辑器中,选中根节点 UI_ActivityTemplate_ExchangeLinearView
  2. 在 Details 面板找到 View 属性
  3. 填入 Lua 模块路径:
    1
    Feature.System.Script.System.Activity.Public.Template.View.UI_ActivityTemplate_ExchangeLinearView

⚠️ 运行时 BaseViewClass.GetLuaViewPathByWndNameOrWidget() 会先查 UIWndNameToLuaPath.lua,查不到就读 UMG 蓝图的 View 属性。活动UMG通常走后者。

2.4 绑定 Widget 组件

在蓝图的 Widget Tree 中创建以下组件,名称必须与模板代码一致:

必须绑定的 Widget(来自源码分析)

Widget 名 UE4 类型 来源代码行 说明
w_btn_Exchange_1 UButton 第7行 self.bp.w_btn_Exchange_1 兑换/追赶按钮
w_img_ActivityTitle UImage 第46行 self.bp.w_img_ActivityTitle 活动标题图
w_img_Activity_BG UImage 第49行 self.bp.w_img_Activity_BG 活动背景图
w_img_BigReward UImage 第52行 self.bp.w_img_BigReward 大奖励图(可选,如无此功能可省略)
w_txt_Desc ULTGTextBlock 第135行 self.bp.w_txt_Desc 活动描述文本
w_size_SubTab USizeBox 第159行 self.bp.w_size_SubTab 子Tab容器(多Tab时显示,无Tab时隐藏)

兑换商店相关 Widget

Widget 名 UE4 类型 来源代码行 说明
w_list_Exchange UGridPanel / UGridSlot 第97行 self.bp.w_list_Exchange 兑换商品列表
w_sizebox_Btn USizeBox 第246行 self.bp.w_sizebox_Btn 兑换按钮容器
w_sizebox_Exchange USizeBox 第246行 self.bp.w_sizebox_Exchange 兑换区域容器
w_sizebox_Retrieve USizeBox 第246行 self.bp.w_sizebox_Retrieve 兑回区域容器(隐藏)

赶/追赶机制相关 Widget(如启用 activityParam)

Widget 名 UE4 类型 来源代码行 说明
w_img_RetrieveIcon UImage 第273行 self.bp.w_img_RetrieveIcon 追赶道具图标
w_img_Exchange UImage 第274行 self.bp.w_img_Exchange 兑换道具图标
w_txt_Retruevetxt ULTGTextBlock 第275行 self.bp.w_txt_Retruevetxt “追回”文本
w_txt_Exchange_1 ULTGTextBlock 第276行 self.bp.w_txt_Exchange_1 “兑换”文本

2.5 父类 TaskView 的 Widget(继承自上层模板)

线性兑换模板继承 UI_ActivityTemplate_TaskView,该层也有 Widget 需求。主要的有:

Widget 名 说明
w_nameSlot_Activity 活动子视图插槽(ActivityModel 通过 AddWidget 加载到此插槽)
w_list_Task 任务列表(展示任务项)
w_img_Background 背景图(可选)

💡 最简单的方法:复制一个已有的线性兑换活动 UMG 蓝图作为基础,然后只替换图片和布局。

2.6 AssetNameMapping 注册

确保新UMG能被资产加载系统找到。在 AssetNameMapping.txt 中添加:

1
ui_activitytemplate_exchangelinearview:/Game/Feature/System/Assets/CommercialRes/Activity/Activity_SpringExchange/Blueprints/UI_ActivityTemplate_ExchangeLinearView.UI_ActivityTemplate_ExchangeLinearView_C

Step 3: 创建实例代码(可选)

判断是否需要

情况 是否需要写代码
纯换皮:逻辑完全相同,只换美术 ❌ 不需要,Step 2 即完成
需要自定义Tab名称或数量 ❌ 不需要,通过 clientParams 配置
需要特殊业务逻辑(如自定义领奖) ✅ 需要创建实例视图
需要独立网络协议 ✅ 需要创建子模型

3.1 创建目录结构

1
2
3
4
5
D:\LetsGoEditor\Editor\LetsGo\Content\Feature\System\Script\System\Activity\Instance\SpringExchange\
├── Model\
│ └── Activity_Instance_SpringExchange_Model.lua
└── SubView\
└── UI_ActivityInstance_SpringExchange.lua

3.2 注册子模型路径

文件:Feature/System/Script/System/Activity/Public/ActivityModelPath.lua

在 ActivityModelPath 表中添加:

1
2
3
4
5
local ActivityModelPath = {
-- ... 已有条目 ...

["Activity_Instance_SpringExchange_Model"] = "Feature.System.Script.System.Activity.Instance.SpringExchange.Model.Activity_Instance_SpringExchange_Model",
}

注意事项:

  • Key(Activity_Instance_SpringExchange_Model)必须与子模型文件名完全一致,区分大小写
  • Value 路径用 . 分隔,对应文件系统的 /
  • 路径末尾不加 .lua

3.3 创建子模型文件

文件:Instance/SpringExchange/Model/Activity_Instance_SpringExchange_Model.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
local Activity_Instance_SpringExchange_Model = _MOE.class(
"Activity_Instance_SpringExchange_Model",
_MOE.SystemSubModel
)

function Activity_Instance_SpringExchange_Model:OnInit()
self.m_activityData = nil
self.m_activityId = 0
end

function Activity_Instance_SpringExchange_Model:OnClear()
self.m_activityData = nil
end

function Activity_Instance_SpringExchange_Model:GetActivityData()
return self.m_activityData
end

function Activity_Instance_SpringExchange_Model:SetActivityData(data)
self.m_activityData = data
end

return Activity_Instance_SpringExchange_Model

关键点:

  • 继承 _MOE.SystemSubModel(不是 _MOE.BaseViewClass)
  • 文件末尾必须有 return Activity_Instance_SpringExchange_Model
  • 通过 _MOE.Models.ActivityModel.subModels.Activity_Instance_SpringExchange_Model 访问

3.4 创建实例视图文件

文件:Instance/SpringExchange/SubView/UI_ActivityInstance_SpringExchange.lua

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
59
60
61
62
63
-- 引用线性兑换模板
local UI_ActivityTemplate_ExchangeLinearView = require(
"Feature.System.Script.System.Activity.Public.Template.View.UI_ActivityTemplate_ExchangeLinearView"
)

local UI_ActivityInstance_SpringExchange = _MOE.class(
"UI_ActivityInstance_SpringExchange",
UI_ActivityTemplate_ExchangeLinearView
)

function UI_ActivityInstance_SpringExchange:OnOpen(Data)
-- 如需多Tab页签,设置 IsNeedShowTab = true
Data.IsNeedShowTab = true

-- 如需调整兑换商城Tab位置(默认第1个)
-- Data.ExchangeTabIndex = 1

-- 调用模板的 Init 方法(注意是 . 不是 :)
self.super:Init(self, Data)

-- 初始化事件
self:OnInitializedEvent()

-- 初始化数据
self:InitData()

-- 注册事件
self:InitEvent()

-- 初始化UI
self:InitUI()
end

function UI_ActivityInstance_SpringExchange:InitData()
-- 先调用父类 InitData
self.super:InitData()

-- 如有自定义数据需求,在此处获取子模型数据
-- local subModel = _MOE.Models.ActivityModel.subModels.Activity_Instance_SpringExchange_Model
-- local data = subModel:GetActivityData()
end

function UI_ActivityInstance_SpringExchange:InitEvent()
-- 调用父类(注册任务更新、商城数据等事件)
self.super:InitEvent()

-- 如有自定义事件监听,在此处添加
-- self.child:AddEventListener(SomeEvent, function() ... end)
end

function UI_ActivityInstance_SpringExchange:InitUI()
-- 调用父类(自动渲染标题、背景、Tab、奖励列表等)
self.super:InitUI()

-- 如有自定义UI渲染,在此处添加
end

function UI_ActivityInstance_SpringExchange:OnClose()
-- 调用父类 OnClose(自动清理 bp、Data、事件监听器等)
self.super:OnClose()
end

return UI_ActivityInstance_SpringExchange

关键编码规则:

  1. self.super:Init(self, Data) — 注意是 :(冒号),传入 self
  2. self.super:InitData() — 先调父类再做自己的逻辑
  3. 不能直接 require 子模型,必须通过 _MOE.Models.ActivityModel.subModels.XXX
  4. 事件监听用 self.child:AddEventListener(OnClose 时自动清理)
  5. ❌ 禁止继承另一个活动实例(只能继承模板)

3.5 注册 UIWndNameToLuaPath(如果用实例视图名)

如果配置表中 activityUIDetail 填的是 UI_ActivityInstance_SpringExchange(实例视图名而非模板名),需要在映射表中注册:

文件:LetsGo/Content/LetsGo/Script/Config/UIWndNameToLuaPath.lua

1
2
3
4
5
local UIWndNameToLuaPath = {
-- ... 已有条目 ...

["UI_ActivityInstance_SpringExchange"] = "Feature.System.Script.System.Activity.Instance.SpringExchange.SubView.UI_ActivityInstance_SpringExchange",
}

⚠️ 如果 activityUIDetail 直接填模板名 UI_ActivityTemplate_ExchangeLinearView,则不需要此步骤——运行时通过 UMG 蓝图的 View 属性定位。


Step 4: 配置统一参数

4.1 通过 clientParams 配置(最常用)

在配置表 Excel 的 clientParams 列填写 JSON 数组:

1
["每日任务", "兑换商店", "成就任务"]

模板代码第 68 行读取:

1
local tabNames = self.Data.clientParams or {}

每个元素对应一个 Tab:

  • index == ExchangeTabIndex 的 → 兑换商店 Tab
  • 其他 → 任务 Tab(按顺序对应 activityTaskGroup 中的任务组)

4.2 通过 activityUnifiedParam 配置(如需自定义参数键值)

如果模板需要额外的参数(如特殊奖励ID、显示控制开关),在 activityUnifiedParam 字段中配置。

新增参数键值时,需要先在 ActivityUnifiedDefine.lua 中注册:

1
2
3
4
5
-- 文件: Public/ActivityUnifiedDefine.lua
local ActivityUnifiedDefine = {
-- ... 已有键值 ...
["SpringExchange_SpecialRewardId"] = "SpringExchange_SpecialRewardId",
}

然后在视图中读取:

1
2
3
4
local paramValue = _MOE.Models.ActivityModel:GetActivityUnifiedParamByKey(
self.ActivityId,
"SpringExchange_SpecialRewardId"
)

Step 5: 红点逻辑

5.1 确认是否需要编码

情况 是否需要编码
活动上新红点 ❌ 服务端自动推送 ActivityReadDotNtf
用户点击后消红 ❌ 服务端自动处理
客户端独立判断红点(如任务完成但未领奖) ✅ 需要编码

5.2 如需客户端消红

在视图的 InitData 中调用:

1
2
3
4
5
6
7
8
9
function UI_ActivityInstance_SpringExchange:InitData()
self.super:InitData()

-- 如果需要客户端主动消红
_MOE.Models.ActivityModel:ActivityClickRedDot_C2S_Msg(
self.Data.id,
_MOE.ServerEnum.ActivityRedDotType.ARDT_New
)
end

5.3 如需刷新大厅红点

修改红点状态后必须调用:

1
2
3
_MOE.Models.RedDotModel:UpdateRedDotByType(
_MOE.CommonDefine.E_RDT_TYPE.Lobby_Activity
)

Step 6: 协议绑定(通常不需要)

判断是否需要

情况 是否需要
活动数据通过 GetActivityInfo 获取 ❌ 不需要
活动有独立的网络通知 ✅ 需要
活动需要监听通用协议(如任务更新) ❌ 模板父类已处理

6.1 在子模型中绑定协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Activity_Instance_SpringExchange_Model:BindAllNotify()
_MOE.NetworkManager:BindNotify(
_MOE.MsgName2Id.SpringExchangeNtf,
function(Msg) self:HandleSpringExchangeNtf(Msg) end
)
end

function Activity_Instance_SpringExchange_Model:HandleSpringExchangeNtf(Msg)
if Msg then
self:SetActivityData(Msg)
-- 通知视图更新
_MOE.EventManager:DispatchEvent(
_MOE.Models.ActivityModel.OnActivityInfoUpdateNtf,
Msg.activity_id
)
end
end

Step 7: 测试验证

7.1 导表检查

1
2
3
# 执行导表,确认无报错
cd D:\LetsGoEditor\Editor\letsgo_common
python export_table.py

7.2 编辑器 PIE 测试

  1. 打开 UE4 编辑器,点击 Play(PIE 模式)
  2. 进入大厅
  3. 打开活动中心
  4. 检查:
    • 活动是否在对应 Tab 下显示
    • 活动名称、子标题、背景图是否正确
    • 点击活动后能进入线性兑换界面
    • Tab 页签是否正确显示(如配置了 clientParams)
    • 任务列表是否正确加载
    • 兑换商店是否正确显示商品
    • 点击兑换按钮能正常兑换
    • 红点是否正确显示
    • 返回后再次进入数据是否保持

7.3 日志检查

PIE 时查看 Output Log,确认无以下报错:

  • AddWidget Error, Load Result is nil → UMG View 属性未设置或路径错误
  • subModels.XXX 返回 nil → ActivityModelPath.lua 未注册或路径错误
  • Key not found → activityUnifiedParam 键值未在 ActivityUnifiedDefine.lua 注册

检查清单

  • 配置表已新增活动行(activityUIDetail 指向模板视图名)
  • activityTaskGroup 已配置正确的任务组 ID
  • activityShopType 已配置正确的商城 mallId
  • clientParams 已配置 Tab 名称列表
  • 导表成功,无报错
  • UMG 蓝图已创建,父类设为 MoeLuaWidget
  • UMG 蓝图 View 属性已设置为模板 Lua 路径
  • UMG 蓝图内 Widget 名称与模板代码一致
  • ActivityModelPath.lua 已注册子模型路径(如有子模型)
  • UIWndNameToLuaPath.lua 已注册映射(如用实例视图名)
  • 编辑器 PIE 测试通过

本文从 LearnByCompany 原始文档 自动同步。