下载 App

资源策略从“全量引用 + 全量预下载”切换为“增强引用 + DWP”

07/23

结论

目前 300/300MB 的主要原因已经明确,不是网络偶发问题,而是同时存在两层“全量加载”:
  1. 构建配置要求所有资源入包,并在启动前全部下载
  2. 客户端启动后,又主动创建了所有遭遇动画的 152 张纹理
所以当前行为本质上是:先把整个资源仓库下载完,再一次性把大量遭遇动画解码进内存
horizontal linehorizontal line

1. 第一根因:配置为“全量引用 + 全量预下载”

当前配置是:
  • groups.default = ["**"]assets/scripts/ 下所有资源全部进入发布包
  • preload_groups = ["default"]:这个组里的所有资源在启动前全部下载
位置:
  • .project/resources.json:4
  • .project/resources.json:8
最近构建日志也明确提示:
构建策略:全量引用——所有本地资源入包
预下载:启用——组内资源启动时预先下载
这正是界面显示 300/300MB 的直接原因。
horizontal linehorizontal line

2. 当前资源体积构成

我统计了 assets/,不包含 .meta
资源类别文件数磁盘体积
PNG 图片247116.67 MiB
MP4 视频40103.53 MiB
合计287220.20 MiB
发布端显示接近 300MB,通常还会包含打包元数据、脚本依赖、字体、资源索引等。虽然与本地裸文件体积不完全一致,但量级是吻合的。

最大的冗余来源:原始 MP4

assets/video/ 占了 103.53 MiB
这些视频主要是生成遭遇动画 PNG 时使用的原始素材,例如:
  • 爷叔填单.mp4:4.55 MiB
  • 贾跃亭汽车.mp4:4.29 MiB
  • 打新中签.mp4:4.26 MiB
  • 雷股出清.mp4:4.24 MiB
  • 壳太重了.mp4:4.17 MiB
目前游戏运行时使用的是提取后的 idle_1..4.png,没有发现脚本播放这些 MP4。由于资源配置是 **,这些制作源文件仍然被全部打包和预下载。
这部分理论上可以直接从运行包中裁掉约 103MB

第二个冗余来源:冷库存档

assets/image/冷库/13.23 MiB
冷库文件用于归档旧版本图片,不参与运行,但因为使用 **,也被加入包并启动下载。

第三个大头:遭遇动画

assets/image/遭遇怪物/
  • 152 张 PNG
  • 磁盘体积:82.36 MiB
  • 其中 148 张为 768×768
这些是有效游戏资源,不一定要从发布包删除,但不应该在进入游戏时一次性全部下载和解码。
horizontal linehorizontal line

3. 第二根因:启动时主动加载全部遭遇动画

所有遭遇动画资源路径都集中登记在:
  • scripts/bullbear/Client.lua:203
  • scripts/bullbear/Client.lua:212
  • 一直延续到 scripts/bullbear/Client.lua:543
仅仅把路径写进 Lua 表,不一定代表运行时立即加载;但客户端启动时执行了下面的逻辑:
  • 遍历所有 ENCOUNTER_MONSTER_VISUALS
  • 遍历每种怪物的 4 帧
  • 对每一帧调用 nvgCreateImage
  • 同时开启 NVG_IMAGE_GENERATE_MIPMAPS
位置:
  • scripts/bullbear/ClientCore.lua:94
  • scripts/bullbear/ClientCore.lua:97
  • scripts/bullbear/ClientCore.lua:99
这意味着即便只把 .project/resources.json 改成 DWP,游戏启动后仍会立即请求全部 152 张遭遇图。
因此,只改预下载配置还不够,要同时改成遭遇动画按需创建。
horizontal linehorizontal line

4. 内存压力也很明显

152 张遭遇图片在磁盘上是 82.36 MiB,但 PNG 进入 GPU 后要按 RGBA 展开。
当前估算:
  • 不带 mipmap 的 RGBA 解码体积:约 337 MiB
  • 启用 mipmap 后再增加约三分之一
  • 遭遇动画纹理可能接近 450 MiB GPU 内存
这还没有计算:
  • 大厅背景
  • 游戏底图
  • 电视图片
  • 牌框
  • 状态图标
  • UI 渲染目标
  • 引擎自身纹理
所以当前问题不仅是下载慢,还可能带来:
  • 手机内存压力
  • GPU 显存压力
  • 首次启动卡顿
  • 纹理解码峰值
  • 低端设备闪退风险
而一轮实际上只显示一个遭遇的 4 帧。只保留当前 4 帧时,遭遇纹理内存大约可以从四百多 MiB 降到十几 MiB。
horizontal linehorizontal line

推荐优化顺序

第一优先级:启用 DWP,取消全量启动预下载

目标是把资源策略从:
全量引用 + 全量预下载
改为:
按引用构建 + 媒体资源按需下载
理想状态:
  • Lua、JSON、XML 等必要脚本和配置启动前准备好
  • 大厅首屏图片只加载大厅需要的资源
  • 遭遇图片在即将出现对应牌面时加载
  • 未使用的原始 MP4、冷库、旧素材不进入运行包
这是收益最大的一步。
但要注意:如果只取消 preload_groups,而不处理 ClientCore.lua 的全量 nvgCreateImage,启动后仍会立即触发 82MB 遭遇图下载。
所以配置与运行时懒加载应一起考虑。
horizontal linehorizontal line

第二优先级:把制作源文件排除在运行资源目录之外

建议把资源分成两个概念:

运行时资源

留在 assets/
  • 当前大厅背景
  • 游戏底图
  • 角色头像
  • 状态图标
  • 技能图标
  • 当前使用的遭遇 PNG
  • 运行时需要播放的视频和音频

制作源文件和归档

不应进入 asset_dirs
  • 提取 PNG 用的原始 MP4
  • 旧版动画帧
  • 冷库存档
  • 未采用的生成图片
  • 中间处理结果
当前最明显的是:
  • assets/video/:约 103.53 MiB
  • assets/image/冷库/:约 13.23 MiB
只排除这两项,运行资源裸文件就能从约 220MB 降到约 103MB
不一定要删除这些文件,只要让它们不参与构建即可。保留在项目中做美术源文件归档没有问题。
horizontal linehorizontal line

第三优先级:遭遇动画改成按牌面加载

推荐的生命周期是:
  1. 进入大厅:只加载大厅和选角资源
  2. 进入休整:加载休整界面、电视、航线资源
  3. 服务端确定下一张牌:提前下载这张牌对应的 4 帧
  4. 进入遭遇:创建这 4 张 NanoVG 图片
  5. 遭遇结束后:
  6. 可以保留最近少量动画作为缓存
  7. 或释放旧动画,只保留当前/下一张
  8. 局终:按需加载结算航海图
这样每次遭遇通常只需要下载大约 2~3MB,而不是首屏下载全部 82MB。
为了避免切换时看到占位图,可以在休整阶段或服务端下发下一事件后提前下载,而不是等到遭遇已经显示才开始。
horizontal linehorizontal line

第四优先级:降低遭遇帧分辨率

目前 148 张遭遇图片是 768×768
是否需要这么高,要看它们在最终屏幕上的实际显示尺寸:
  • 如果怪物大多数只显示在约 200~350px
  • 那么 768×768 通常偏大
  • 手机横屏下更可能存在明显浪费
可以考虑:
使用环境建议候选
主要显示约 200~300px384×384
需要兼顾高 DPI512×512
怪物确实会放大到接近全屏保留 768×768
如果全部从 768 降到 512:
  • 像素数量降到原来的约 44%
  • 解码内存可由约 337 MiB 降到约 150 MiB
  • PNG 下载体积通常也会显著下降
不过这一项应该排在 DWP 和懒加载之后。因为即使保持 768,只按需加载当前 4 帧,问题也已经会大幅缓解。
horizontal linehorizontal line

第五优先级:检查 mipmap 是否必要

当前所有遭遇动画都使用:
text
NVG_IMAGE_GENERATE_MIPMAPS
scripts/bullbear/ClientCore.lua:97
如果遭遇怪物始终以接近固定尺寸进行 2D 展示,并没有从极远处缩小到很小,mipmap 的收益可能有限,却会:
  • 增加约三分之一纹理内存
  • 增加纹理创建成本
  • 增加初始化工作量
但是否关闭需要实机观察缩放时的清晰度与闪烁,不能仅凭体积直接决定。
horizontal linehorizontal line

建议实施方案

方案 A:低风险快速优化

适合先验证加载时间:
  1. 原始视频和冷库不参与运行包
  2. 取消全量预下载,启用 DWP
  3. 遭遇动画从“启动时全部创建”改成“当前牌面才创建”
  4. 保持现有 PNG 尺寸和画质不变
预计效果:
  • 发布包减少至少约 116MB
  • 首屏不再显示 300MB 全量下载
  • 大厅可能只需要下载十几 MB 以内的资源
  • 遭遇资源随后按每张牌约 2~3MB 加载
  • GPU 纹理峰值大幅下降
这是我最推荐的第一阶段。

方案 B:进一步压缩

方案 A 稳定后再做:
  1. 768×768 遭遇帧评估降到 512×512
  2. 判断是否取消 mipmap
  3. 评估 4 帧合并图集
  4. 压缩大厅和电视等大图
  5. 统计每个界面的实际下载量和纹理内存
图集主要减少文件数量和资源请求,不一定明显减少解码后显存;真正降低显存的仍然是分辨率和按需释放。
horizontal linehorizontal line

验收时应看什么

优化后不能只看“能进入游戏”,建议检查以下指标:
  1. 构建日志
  2. 不再出现“全量引用”
  3. 不再出现“预下载 default 全组”
  4. 冷启动
  5. 清缓存后进入大厅的下载量
  6. 热启动
  7. 第二次进入是否复用缓存
  8. 首个遭遇
  9. 是否只下载当前怪物的 4 帧
  10. 连续多局
  11. 内存是否持续增长
  12. 低端手机
  13. 是否仍有纹理创建卡顿或闪退
  14. 资源完整性
  15. 随机抽到任意牌面时,对应动画都能正常出现
horizontal linehorizontal line

最核心的判断

当前不是“图片太多”这么简单,而是:
制作源文件、运行资源、冷库存档全部放进同一个全量预下载组;客户端还在初始化阶段一次性创建所有遭遇纹理。
优先解决“资源边界 + DWP + 遭遇懒加载”,比先压缩每张图片更有效。全程仅分析,没有修改代码或配置。

已完成方案 A:低风险快速优化。

已实施

  • 将资源策略从“全量引用 + 全量预下载”切换为“增强引用 + DWP”:
  • .project/resources.json:4
  • 构建根资源改为 scripts/**
  • preload_groups 已清空
  • 原始 MP4、冷库及其他未引用素材不会再进入运行包。
  • 取消启动时一次性创建全部 152 张遭遇动画纹理。
  • 收到当前牌面 SHOW_EVENT 后,只下载并创建对应的 4 帧:
  • scripts/bullbear/ClientCore.lua:41
  • scripts/bullbear/ClientCore.lua:817
  • 切换牌面、离开玩法阶段或客户端停止时:
  • 取消旧下载
  • 调用 nvgDeleteImage 释放旧纹理句柄
  • 只保留当前遭遇的四帧
  • scripts/bullbear/ClientCore.lua:26
  • scripts/bullbear/ClientCore.lua:184
  • 下载过程中继续显示既有海星占位,不阻塞牌面流程:
  • scripts/bullbear/ClientRender.lua:44

构建结果

最新构建日志确认:
text
构建策略: 增强引用 预下载: 未启用 扫描本地资源: 729 个文件 发现可达资源: 58 个 分组总资源: 59 个 项目资源大小: 93.24 MB
此前本地 assets/ 裸资源约为 220.20 MiB;当前构建统计为 93.24 MB。更重要的是,这 93.24MB 不再要求进入游戏前一次性下载,而是由 DWP 按实际使用加载。

验证

  • Lua LSP:0 Error
  • 官方 UrhoX 多人构建:成功
  • 多人配置保持:
  • 后台匹配开启
  • free_match_with_ai
  • 4 人
  • 3 秒 AI 补位
  • 秒开模式开启
  • 未执行 Git 提交。

建议实机验收

请清除游戏缓存后重点验证:
  1. 首次进入大厅不再显示 300/300MB
  2. 进入第一张遭遇牌时,海星占位短暂出现后替换为对应动画。
  3. 连续切换不同遭遇,动画都能正确显示。
  4. 断线重连到 PLAYING/REVEAL 时,当前遭遇动画能重新加载。
  5. 连续玩多局后内存不持续上涨。
牌面文字反复重新布局和渲染
调查结论 问题已查清:牌面文字本身没有异常,也不是 NanoVG 重复绘制。真正原因是每名玩家或 AI 投币后,客户端都会销毁并重新创建整个战斗 UI。 左右牌文字、牌框、顶部头像、底部状态栏都在同一棵 UI 树中。因此本来只需要更新底部投币槽,却连带让红框内的牌面文字反复重新布局和渲染,表现为“闪烁几下”。 直接证据 Client.BuildPlayingUI() 每次先销毁现有根节点: scr
官方
理性值跨局持久化
1. 数据常量(RationalityData.lua) M.INITIAL_VALUE = 100 -- 新玩家初始理性值 M.MAX_VALUE = 100 -- 理论上限 M.REMEDY_MAX_VALUE = 90 -- 药剂恢复封顶 M.REMEDY_REQUIRED_MAX_VALUE = 89 -- 药剂可用阈值(理性≤89) M.REMEDY_COST = 3 -- 药剂价格(宝
官方
卡牌:市场调研
skill_market_research 当前仅完成了: 加入随机技能池; 可以在技能节点被选中; 有新绘制图标; 有 Tooltip 设计说明; 能作为技能徽章显示。 但“选择后显示实时人数,并允许一次改选”的玩法逻辑尚未接入。 代码证据 1. Tooltip 明确标记为“设计中” scripts/bullbear/Client.lua:830 当前内容仍是: 技能 · 侦察 + 改选 · 设
官方
闪跳问题
下面是这轮三个问题(音量滑块、拖动闪跳、选角页闪跳/白屏)的可复用经验与对应关键代码。代码均为磁盘最终落地版本。 经验一:拖动控件别在回调里重建整棵树 滑块/开关这类"边拖边回调"的控件,onChange 里只能原地改值,绝不能触发 BuildXxxUI() 整树重建——否则正在拖的滑块会被销毁,拖动中断。音量百分比标签也是同理,用闭包持有引用做原地 SetText。 scripts/bullbe
官方
UI按键动画设计
一、本次反复失败的根因(三个坑) 现象 根因 按钮完全不可见(空白/小白点) 入场动画把 scale/alpha 初始设 0,靠 Update 驱动 Tween 补到 1;但该模板页面级 Update 时序不可靠,补间不推进 → 永远停在 0 背景/按钮颜色反转 用了 nvgRGBA(r,g,b,255) 整数版,出现通道映射异常 矩形在、文字消失 文字色≈填充色;或 DrawStrokedTex
官方
【牌】兴登堡凶兆
这张卡的主题很适合《友谊的小船》,但按原始数值直接落地会有一个关键问题:右牌严格优于左牌,四人局会趋向全员选右,博弈不成立。 先评估原方案 选择 未扎堆 同侧≥3人 结论 左:追高 -15万 -40万 始终比右牌更痛 右:抄底 -10万 -25万 任意人数下都更优 无论同侧人数是多少,右牌都少亏: 未扎堆:右牌少亏 5万 扎堆后:右牌少亏 15万 所以理性玩家没有选择左牌的理由;最终通常会四人全选
官方
WASM 崩溃
nvgCreateImage 在 WASM 环境下无法正确加载图片文件(中文路径/资源未打包进虚拟文件系统),返回看似正数但实际无效的纹理句柄。Lua 中 0/-1 都是 truthy,if 判断通过后把无效句柄传给 nvgImagePattern,C 层访问空指针 → WASM 内存越界。 彻底解决方案:纯矢量绘制,零文件 I/O 完全放弃图片加载,用 NanoVG 基础图元(圆/椭圆/描边)绘
官方
大厅校准模式
一、整体架构 校准系统由四层组成: 层 文件 职责 状态容器 Client.lua 保存 lobbyCalibrationMode_/Drag_/Slots_/RestorePending_/SaveStatus_、路径常量、管理员标记 拖拽 + UI ClientUILobby.lua 指针事件包装、归一化坐标、实时预览、设置面板 序列化 ClientStorage.lua 本地 pref 读写
官方
背景音乐持久化方案
问题演进 轮次 方案 失败原因 1 File("save.pref", FILE_WRITE) WASM MEMFS 刷新即丢失 2 clientCloud:SetInt("music_enabled", 1/0) 写 0 被后端吞掉;HTTP 请求刷新时被浏览器取消 3 clientCloud:Set("music_enabled", "off") 同上,HTTP 不可靠 4 serverClo
官方
拖到合成目标才留位
错误原因 我在上一轮加回收功能时,为了"修位置回正"加了 else 分支:当牌既没拖到回收区、也没合成目标时,强制把牌弹回原位。这破坏了 Stacklands 的基本交互——拖到空地就该留在那里。 -- ❌ 错误版本(多余的 else 导致拖到空地也弹回) if inRecycle and not card.locked then ...回收溶解... elseif self._mergeTar
官方