赋能UE运行态编辑平台: 网络图片下载的插件改造与复盘

[复制链接]
发表于 昨天 17:06 | 显示全部楼层 |阅读模式
配景

在数字孪生、数据看板、装备监控这类 UE 项目里,常常会遇到一个需求:运行时从服务器下载图片,然后表现到 UI、材质或场景对象上。
比如我们如今在做的是一个UE 运行态下的POI点位资产编辑平台,要可以大概编辑点位的名称,信息,图标,尺寸等,如下图所示,此中图标是从网络加载的:

此中图片资源在平台服务器端,用户自已上传图标,如下图所示:

单张图片下载看起来不复杂,但真正做成一个可复用插件后,会遇到不少 UE 特有的题目:异步回调、蓝图节点、运行时纹理、GC 生命周期、图片格式辨认、中文 URL 编码,以及跨项目打包后的偶发瓦解。
这篇文章复盘一次 ImageDownloader 插件的改造过程。
1. 为什么不做同步下载

最开始的需求是批量下载图片。直觉上好像可以做一个“同步下载”:按序次下载 A、B、C,全部完成后再继续。
但在 UE 运行时,这不是一个好选择。
网络哀求耗时不可控,假如在 GameThread 上同步期待,游戏画面、UI 相应都会被壅闭。多张图片连续下载时,卡顿会叠加;假如哀求超时或失败,期待时间更不可控。
以是终极方案不是同步下载,而是批量异步下载:

  • HTTP 哀求异步发起
  • 图片二进制解码放到背景线程
  • UTexture2D 创建回到 GameThread
  • 蓝图通过回调吸收单张完成和全部完成效果
焦点原则是:耗时数据处置处罚离开 GameThread,UObject 和渲染资源操纵回到 GameThread。
2. 批量下载的焦点计划批量下载节点吸收一个 URL 数组,然后为每个 URL 创建独立 HTTP 哀求。

每张图片完成后触发一次单图回调:
  1. OnImageDownloaded(Index, ImageURL, Texture)
复制代码
全部图片都竣事后触发总回调:
  1. OnAllComplete(Textures, SuccessStates)
复制代码
这里有一个很紧张的计划:效果数组保持输入序次。
比如输入是:
  1. 0: A.png
  2. 1: B.png
  3. 2: C.png
复制代码
纵然实际完成序次是 B、C、A,终极效果仍然写回原始索引:
  1. Textures[Index] = Texture;
  2. SuccessStates[Index] = Texture != nullptr;
复制代码
如许蓝图侧不必要自己维护完成计数,也不必要处置处罚异步完成序次,只要在 OnAllComplete 里读取效果数组即可。
3. 蓝图异步节点的一个潜伏限定

这个插件最开始利用 UBlueprintAsyncActionBase 实现尺度蓝图异步节点。
它的工作方式大抵是:

  • 静态工厂函数创建异步署理对象
  • 蓝图绑定输失变乱
  • UE 调用 Activate()
  • 异步任务启动
  • 完成后广播委托
这里有一个容易踩坑的点:在 UE 5.2 中,异步节点固然可以有多个 BlueprintAssignable 输失变乱,但数据输出 pin 通常只按照第一个委托署名天生。
也就是说,假如第一个变乱是:
  1. Index, ImageURL, Texture
复制代码
第二个变乱是:
  1. Textures, SuccessStates
复制代码
蓝图节点上大概只表现第一组数据 pin,导致总完成变乱拿不到数组。
办理方式有两个。
第一种是把两个变乱同一成同一个完备署名,让节点一次性天生全部数据 pin。
第二种是新增“变乱参数模式”,利用动态单播委托作为平凡函数参数,让蓝图直接接 Create Event 或自界说变乱。
终极插件生存了两套入口:
  1. Download Images Async
  2. Download Images With Events
复制代码
前者得当快速利用,后者得当必要明确绑定自界说变乱、差别回调署名更清楚的场景。
4. 运行时创建 Texture2D 的瓦解

这次改造里最关键的瓦解来自运行时纹理创建。
原代码大抵是:
  1. UTexture2D* Texture = UTexture2D::CreateTransient(Width, Height, PF_B8G8R8A8);
  2. Texture->GetPlatformData()->Mips.Add(new FTexture2DMipMap());
复制代码
题目在于:CreateTransient() 已经创建好了第 0 层 mip。
背面再手动 Mips.Add(),会追加一个没有精确设置尺寸和 BulkData 的无效 mip。等 UpdateResource() 构建纹理资源时,UE 读取到无效 mip,就大概触发 GetMipData failed、invalid GUID,乃至直接断言瓦解。
精确做法是直接写入已有的 Mips[0]:
  1. UTexture2D* Texture = UTexture2D::CreateTransient(Width, Height, PF_B8G8R8A8);
  2. Texture->SRGB = true;
  3. Texture->NeverStream = true;
  4. void* TextureData = Texture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
  5. FMemory::Memcpy(TextureData, RawData.GetData(), RawData.Num());
  6. Texture->GetPlatformData()->Mips[0].BulkData.Unlock();
  7. Texture->UpdateResource();
复制代码
同时,图片解码格式要和纹理像素格式匹配。
假如纹理格式是:
  1. PF_B8G8R8A8
复制代码
解码时就应利用:
  1. ERGBFormat::BGRA
复制代码
否则固然不肯定瓦解,但大概出现红蓝通道庞杂。
5. 图片格式不要只看 URL 后缀

早期格式判定只依赖 URL 后缀,比方 .png、.jpg。
但实际项目里常常遇到这些环境:

  • URL 没有扩展名
  • URL 带查询参数
  • CDN 地点后缀不可靠
  • 服务端返回格式和文件名不划一
因今后续改成优先根据图片二进制数据辨认格式:
  1. ImageWrapperModule.DetectImageFormat(ImageData.GetData(), ImageData.Num());
复制代码
假如二进制检测失败,再用 URL 后缀兜底。
这个改动不影响蓝图接口,但能显着进步下载器的容错本事。
6. 中文 URL 必要单独处置处罚

项目里还遇到过一种环境:欣赏器可以打开图片,但插件下载失败。
比方:
  1. http://192.168.31.70:8090/images/球机.png
复制代码
欣赏器通常会自动转成:
  1. http://192.168.31.70:8090/images/%E7%90%83%E6%9C%BA.png
复制代码
UE HTTP 哀求不会总是帮你做这件事,以是中文路径必要举行 UTF-8 百分号编码。
但不能直接对整个 URL 调用通用编码函数,否则 http://、/、: 也会被转义,URL 结构会被粉碎。
终极做法是:只编码中笔墨符,生存 URL 结构字符稳固。
如许:
  1. http://192.168.31.70:8090/images/球机.png
复制代码
会被转换为:
  1. http://192.168.31.70:8090/images/%E7%90%83%E6%9C%BA.png
复制代码
7. 异步对象生命周期比空指针更伤害

在 UE 里,许多瓦解不是真正的 nullptr,而是 UObject 被 GC 后,异步回调还在访问旧对象。
这类题目本质是悬空指针或 use-after-free。
风险紧张来自两点:

  • 异步署理对象通过 NewObject 创建,但没有显式生命周期托管
  • 背景线程或 GameThread lambda 直接捕捉裸 this
更稳的做法是:

  • 利用 RegisterWithGameInstance(WorldContextObject) 托管异步署理
  • 完成后调用 SetReadyToDestroy()
  • 跨线程 lambda 利用 TWeakObjectPtr
  • 回到 GameThread 后先判定对象是否仍然有用
运行时创建的 UTexture2D 也一样必要可达引用。假如蓝图只是暂时拿到纹理并设置 UI,但没有生存到成员变量或数组,后续也大概被 GC 影响。
8. 大块图片数据转达要留意拷贝本钱

图片解码后的原始像素数据通常很大。
比方:
  1. 2048 x 2048 x 4 = 16 MB
  2. 4096 x 4096 x 4 = 64 MB
复制代码
假如每次跨函数、跨 lambda 都复制一份,内存峰值和性能开销都会上升。
因此在解码后转达 TArray 时,利用 MoveTemp 是公道的:
  1. CreateTextureOnGameThread(Width, Height, MoveTemp(RawData), OnComplete);
复制代码
它不是为了“让代码能跑”,而是为了克制额外复制大块图片数据。
在图片下载、解码、创建纹理这种高吞吐链路里,移动语义优劣常实用的工程优化。
总结

这次图片下载插件改造,看起来只是“下载图片并表现”,实际涉及了 UE 运行时开发的多个关键点:

  • 不要在 GameThread 同步期待网络哀求
  • 背景线程只做纯数据处置处罚
  • UTexture2D 创建和蓝图广播回到 GameThread
  • 批量异步效果要按输入索引回填
  • 蓝图异步节点存在委托 pin 天生限定
  • CreateTransient() 后不要手动追加无效 mip
  • 图片格式优先从二进制头辨认
  • 中文 URL 必要精确百分号编码
  • UObject 异步署理必须思量 GC 生命周期
  • 大块像素数据转达应只管利用移动语义
真正稳固的运行时图片下载器,不但是 HTTP 哀求乐成就竣事了。它还要处置处罚线程、纹理资源、蓝图节点、生命周期和各种真实项目里的输入非常。
这些细节做好以后,插件才更得当从一个项目迁移到另一个项目,也更能经得住打包环境和复杂蓝图流程的磨练。
末了,关注公号“ITMan彪叔” 可以添加作者微信举行互换,实时收到更多有代价的文章。

免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表