自由学习记录(162)

一个已经放在关卡里的 Cine Camera Actor、角色、灯光拖进 Sequencer,这种本质上是在做"对场景对象的时序控制"这类对象在 Sequencer 里通常叫 Possessable。

这种情况下,Sequencer 不是脱离场景,而是在"驱动场景里已经存在的 Actor"。

第二类,是 Sequencer 自己生成和管理的物体。

比如有些相机、特效、临时物体,可以作为 Spawnable 存在,它不是你事先摆在关卡里的常驻 Actor,而是 Sequencer 播放时生成,结束时销毁。这种就更接近你说的"独立于场景物体设置",因为它不依赖你先在关卡里手动摆一个现成对象。

Camera Cuts

控制当前镜头切换,本质是 Sequencer 自己的剪辑逻辑。

Event Track

触发蓝图事件、函数调用,不一定直接改某个场景物体的 transform。

Audio Track

播声音。

Level Visibility Track

控制子关卡显示隐藏。

Subsequence Track

嵌套别的序列。

这些都说明:Sequencer 不只是"给场景物体打关键帧",它本身就是一个独立的编排系统。

更完整一点是:

  • Widget 自己不负责把自己塞进屏幕
  • Widget 自己不决定自己压在谁上面
  • Widget 自己也最好不偷偷改自己的外部布局规则
  • 这些外部显示控制统一由 AMirrorCapture``DisplayActor 来做

"MetaHuman Creator 被弃用" ,主要指的是原来的网页式 MHC 工作流正在退出 ,因为 Epic 已经把 MetaHuman 创建功能整合进 Unreal Engine 里了;在 5.7 的文档体系里,MetaHuman 的创建入口已经是"直接在 Unreal Engine 中创建高保真数字人",而 5.7 release notes 也继续把这套能力当成引擎内工作流来写。Epic Games Developers+2Epic Games Developers+2

Fab 页面写得很明确:它是把当年 MetaHuman Creator 里的那组灯光预设,作为可下载内容包提供给 UE 使用;文档也把它描述为一个"offline"的工程 / content sample,目的是让你在 Unreal Engine 里复现和测试那些灯光环境。

Creator 作为独立 web 产品的概念基本被 UE 内置工作流替代了

在 UE5.7 里做人和调人,不需要再执着于旧网页 Creator。

Redirector

你移动资源后,UE 常常不会立刻把所有旧引用直接改死,而是先在旧位置留一个 Object Redirector

旧路径 → 新路径

这样别的资源暂时还按旧路径找,也能跳转到新位置,不至于立刻全炸。

先允许旧引用继续活着,再逐步 Fix Up Redirectors 把引用真正改到新地址。

overflow-visible! 复制代码

GameViewportClientClassName=/Script/ZMDRender.ZMDGameViewportClient

这行的作用是告诉引擎:

"启动时不要再用默认的 UGameViewportClient,改用我项目里的 UZMDGameViewportClient。"

这和你前面做的"全屏镜像 / 左右翻转显示"需求是直接相关的。因为这类全局显示层处理,通常要挂在 GameViewportClient 这一层,只有在配置里注册了这个类,UE 启动时才会实例化你的自定义 viewport client。

只改 .h/.cpp 不够,不配 DefaultEngine.ini,引擎根本不知道要用它。

只要进入游戏运行态,尤其是 PIE、Standalone、打包后的游戏,都会走 GameViewportClient

如果你在这个类里做了"整屏左右翻转",那通常会表现为:

游戏画面被翻转。

Editor 本体一般不会整体受你这个类控制,但 Editor 里的 Play 窗口会受影响。

UE 编辑器本身的各种面板,像 Content Browser、Details、Blueprint Editor、主菜单这些,是 Slate 编辑器 UI,不是你的 GameViewportClient 在管。

所以编辑器外壳通常不会被你改坏成"整个编辑器镜像"。

编辑器里按 Play 后出现的游戏视口,是会走项目的 GameViewportClient 的。

PIE

Play In Editor。也就是你在编辑器里点 Play 后,编辑器内部跑起来的那套"游戏实例"。

"引擎在启动创建 viewport client 时,本来就会先看配置里指定要实例化哪个类;你现在把这个类名从默认值换成了你项目里的类。"

引擎原本就支持这种替换。

你是在走官方的 class hook / class override 机制。

不是修改 Unreal Engine 安装目录下的源码,也不是 patch 二进制。

  • 启动到某一步时,需要创建 GameViewportClient

  • 这时候它会查配置:GameViewportClientClassName

  • 如果没配,通常用默认的引擎类。

  • 如果配了你的 /Script/ZMDRender.ZMDGameViewportClient,就实例化你的类。

没有真实的 Script 文件夹给你去资源管理器里找。

这里的:

overflow-visible! 复制代码

INI
/Script/ZMDRender.ZMDGameViewportClient

是 UE 的反射路径 / 类路径,不是磁盘目录路径。

也就是说:

/Script/ 不是你项目目录下的一个真实文件夹。

它更像 UE 内部的一种命名空间前缀,表示"这是一个由脚本反射系统可见的类,来自某个模块"。

这串路径可以拆成:

/Script/

表示"在反射系统里,从脚本模块空间查类"。

ZMDRender

你的模块名,通常就是项目主模块名。

ZMDGameViewportClient

这个 UClass 的类名。

所以它真正对应到磁盘上,通常是你项目源码里的这些地方:

Source/ZMDRender/ZMDGameViewportClient.h
Source/ZMDRender/ZMDGameViewportClient.cpp

也就是说:

反射路径:
/Script/ZMDRender.ZMDGameViewportClient

源码文件路径:
Source/ZMDRender/ZMDGameViewportClient.h/.cpp

这两者不是一回事,只是前者最终"映射到"后者编译出来并注册的那个 UClass。

磁盘文件是你写代码的地方。
/Script/... 是引擎运行时通过反射系统识别这个类的"注册名"。

为什么叫 Script,哪怕你写的是 C++?

因为 UE 这套命名历史上是按"可反射、可被引擎脚本系统识别"的对象空间来命名的,不是指你真的在写 Python/Lua 那种 script 文件。

UCLASS、UFUNCTION、UPROPERTY 这些经过 UHT 处理后,都会进入这套反射系统里。

所以你在 Windows 资源管理器里找不到:

overflow-visible! 复制代码

YourProject/Script/ZMDRender/...

这是正常的。

你真正该找的是:

overflow-visible! 复制代码

你的项目目录/Source/ZMDRender/

"Coachella" is one of the world's largest, most famous annual music and arts festivals held in Indio, California. The name originated as a 1900s map typo of the Spanish word conchilla ("little shell") . Today, it functions as shorthand for a premier, trend-setting desert festival renowned for diverse music, fashion, and massive art installations.

被视为全球最具代表性、规模最大、赢利能力最高的音乐节之一。

除了音乐,现场还有大型艺术装置,且因其独特的户外风格,吸引许多时尚达人穿搭出席,被称为"网红发照片的秀场"。

后处理材料通常只能处理场景,不直接处理 Slate/UMG

这点特别关键。因为如果场景和 UI 真是混成一个统一 color buffer 一起走完,你的 post process 就应该把 UI 一起处理掉。但实际不是这样:

  • 你在 post process 里做色调、模糊、翻转

  • 往往只作用到场景图像

  • UMG 还是后来叠上去的

这正是"分阶段"的一个非常直观证据。

用 ini 去改 viewport / window / Slate 相关的客户端显示行为,那它更接近:

在最终客户端显示层动手

而不是"只在场景渲染结束、UI 尚未开始前"那个点动手。

在 JetBrains Rider 中实现视口(编辑器)左右滑动,主要依靠鼠标滚轮或触控板手势。最常用的方法是按住 Shift 键的同时滚动鼠标滚轮,或者使用触控板进行水平双指滑动。此外,也可以在设置中自定义滚轮行为来实现水平滚动。

I fixed the compile error in UZMDGameViewportClient::Tick() by renaming the local World variable to avoid hiding UGameViewportClient::World. Please recompile and test once more.

你写在磁盘上的,首先只是普通 C++ 文件。比如:

Source/ZMDRender/ZMDGameViewportClient.h
Source/ZMDRender/ZMDGameViewportClient.cpp

如果这个类只是纯 C++ 类,那 UE 其实不能用字符串 /Script/ZMDRender.ZMDGameViewportClient 去配置式创建它。

之所以现在能这样写,前提通常是它是一个 UCLASS(),并且继承自 UGameViewportClient 这样的 UObject 体系类。

UCLASS()

class ZMDRENDER_API UZMDGameViewportClient : public UGameViewportClient

{

GENERATED_BODY()

...

};

GENERATED_BODY()

说明会有一段 UHT 生成代码插进来。

Editor 下的场景视口

它有自己的 FEditorViewportClient,但它外面也是挂在 Slate 里的,比如一个 SViewport / 编辑器 tab / 窗口系统。

Slate 是"外层容器/框架",client 是"视口行为和渲染逻辑的控制者"。

给承载游戏画面的那个 Slate 视口 widget 做了 RenderTransform 镜像。

UE 桌面端的窗口和视口展示,大多都经过 Slate。

  • Play In Selected Viewport:是

  • Play In New Editor Window:也是

  • Standalone Game:通常也还是

但区别在于它们的复用关系不同,不是"是不是 Slate"这个维度不同。

UE 选择显式写出来,主要是因为它想把"生成代码插入点"这件事写死在源码里,而不是隐式规则。

原因有几个:

其一,历史兼容性

UE 这套宏系统不是今天重新从零设计的,而是很多年一路演化过来的。中间还有过:

  • GENERATED_UCLASS_BODY()

  • GENERATED_BODY()

  • 老版本构造函数约定

  • 不同 UHT 生成代码风格

如果改成"只要看见 UCLASS 就默认往类体某处塞东西",会影响大量旧代码和工具链。

  1. 它操作的是 Slate 视口控件,不是场景渲染结果

  2. 这个 SViewport 很可能在 Editor / PIE 生命周期之间复用

    • 所以第一次 Play 翻转后,退出 PIE,那个 Slate transform 没有被你预期的生命周期完整接管
    • 结果就是你看到的:
      • 初始 Editor 正常
      • 第一次 Play 反向
      • 退出后 Editor 不恢复
    • 这说明变换挂在了共享的视口控件状态上,而不是挂在"只属于本次 Play 的场景渲染结果"上
  3. UGameViewportClient 不是你要的正确切入点

    • 你要的是:场景渲染完成后、Slate 合成 UI 之前 做图像翻转
    • UGameViewportClient 这里更接近"管理游戏视口与 Slate 包装"的层
    • 所以你现在拿它去改 SViewport,天然就会有生命周期和 Editor/PIE 共享状态问题

全局 Shader 注册发生得太晚了,已经错过了 Unreal 初始化 Shader 类型的阶段,所以引擎直接断言中止。Assertion failed: !AreShaderTypesInitialized() 来自 Shader.h

Shader type was loaded too late, use ELoadingPhase::PostConfigInit on your module to cause it to load earlier.

  • 引擎已经直接告诉你修法:把模块加载阶段从 "Default" 改成 PostConfigInit
  • 也就是说,当前模块 ZMDRender 加载太晚。
  1. 调用栈里最关键的一行是:

  2. 调用栈里出现 LiveCoding 相关模块

    • 说明这次很可能是在 Live Coding 热重载 或补丁模块加载时触发。
    • 注意全局 Shader 这类静态注册代码,对 Live Coding 比较敏感;即使主模块设置正确,热重载阶段也可能因为补丁 DLL 的装载时机而出问题。

你的模块在 ZMDRender.uproject 里配置为:

而你在 ZMDMirrorSceneViewExtension.cpp 中定义了全局 Shader:

这类 Shader 类型会在模块装载时通过静态初始化注册。由于模块是 Default 阶段加载,时机晚于引擎允许的 Shader 注册窗口,所以断言。

你的项目模块 ZMDRender 里声明了全局 Shader FZMDMirrorSceneFlipPS,但模块加载阶段太晚,导致 IMPLEMENT_GLOBAL_SHADER(...) 的静态注册发生在 Shader 系统初始化之后,所以崩溃。

Live Coding 可能继续干扰

因为调用栈里是 UnrealEditor_ZMDRender_patch_0,这明显是 Live Coding 生成的补丁模块。

对于 IMPLEMENT_GLOBAL_SHADER(...) 这类代码,常见建议是:

  • 改完模块加载阶段后,不要只靠 Live Coding 验证
  • 关闭编辑器
  • 清理并完整重编译
  • 再重新启动编辑器

否则补丁 DLL 仍可能在错误时机被装载。

2. Shader 目录映射也要确认

你当前使用的是路径 "/Project/ZMDMirrorSceneFlip.usf",并且工程里确实有 Shaders/ZMDMirrorSceneFlip.usf

这通常没问题,但前提是项目 Shader 目录映射正常。当前这次崩溃的主因不是路径找不到 ,而是注册时机错误

看不到 shader 文件夹是正常的。

原因

Content Drawer 只显示 /Game 下面的内容资源,也就是项目里的 Content/ 目录中的资产,例如材质、蓝图、贴图、地图等。

而你现在的 Shader 文件放在项目根目录下的 Shaders/ZMDMirrorSceneFlip.usf,它不是 UAsset 资源 ,也不属于 Content/ 目录,所以不会出现在 Content Drawer 里。

你当前项目里的实际情况

我刚确认过,项目里确实存在这个文件:

所以:

  • 文件是存在的
  • 只是不会在 Unreal 的 Content Drawer 里显示

应该在哪里看它

这种 .usf 文件应该在:

  • VSCode / Rider / Visual Studio 的工程目录里看
  • 或者操作系统文件管理器里看项目目录

也就是直接看:

这和之前那个报错的关系

之前的崩溃重点不是"编辑器里没显示 Shader 文件夹",而是:

所以 Content Drawer 看不到 Shaders/ 并不说明 Shader 没生效,也不是那次断言的根因。

正确认知可以总结为

复制代码
Content Drawer 只管 Content 资源
.usf 是源码级 Shader 文件
项目根目录的 Shaders 文件夹默认不会显示在 Content Drawer
  • 对的部分:它确实不是普通 Content 资源,而是工程源码体系的一部分。
  • 不对的部分 :它不是 C++ 模块源码的一部分,不能像 Source/ZMDRender/ZMDRender.cpp 那样被 MSVC 直接编译进 DLL;它走的是 Unreal 的 Shader 注册、预处理、编译、缓存流程。

这两个东西的关系像这样:

Source/ZMDRender/ZMDMirrorSceneViewExtension.cpp

IMPLEMENT_GLOBAL_SHADER

Shaders/ZMDMirrorSceneFlip.usf

Unreal Shader Compiler

GPU Shader

也就是:

  1. C++ 里声明一个 Shader 类型,例如 FGlobalShader 的子类。
  2. 用**IMPLEMENT_GLOBAL_SHADER(...) 把这个类型和某个 .usf 文件绑定**。
  3. Unreal 在合适时机编译 Shaders/ZMDMirrorSceneFlip.usf 里的入口函数 MainPS
  4. 渲染时++由 C++ 代码去调这个 GPU Shader++。

为什么它不在 Source 目录里

因为职责不同:

1. Source/ 是模块边界

比如你的运行时模块 ZMDRender,里面放的是:

  • 类定义
  • 模块启动
  • 视图扩展
  • 渲染调度逻辑

例如:

2. Shaders/ 是 Shader 源码边界

例如 Shaders/ZMDMirrorSceneFlip.usf 里写的是 HLSL 风格代码:

这些不是 C++,而是给 GPU 用的程序。

编辑器能把它们列在:

  • C++ Classes/ZMDRender/...

这表示:

  • 它们是 Unreal 可反射的 C++ 类型
  • 不是说它们已经变成了 Content 资源

Unreal 为了统一编辑体验,把很多"可被编辑器识别的东西"都放进内容浏览器体系里展示,包括:

  • Content
  • C++ Classes
  • Engine

这是一种 UI 层面的统一入口,不是说底层存储模型都一样。

顺着这个逻辑,Shader 也是类似情况

Shaders/ZMDMirrorSceneFlip.usf 更偏"源码",甚至比 C++ Classes 还不像资源:

所以:

  • C++ Classes源码类型被编辑器展示
  • Shaders/源码文件但通常不在编辑器内容视图展示
  • 二者都不是传统意义上的 Content 资源

比如音乐节、演唱会、比赛、节目单里,lineup 就是在说这次有哪些艺人、乐队、嘉宾参加。

"Coachella 2026 lineup, headliners & schedule"

lineup 偏"谁会来"

schedule 偏"几点上"

英语里很多姓氏本来就来自职业、地点、外貌或祖先身份,后来固定成家族姓氏了。Carpenter 这种非常常见,和这些是一个路数:

Smith 原义是铁匠

Baker 原义是面包师

Taylor 原义是裁缝

Miller 原义是磨坊工

Fisher 原义是捕鱼的人

所以 Sabrina Carpenter 的意思不是"萨布丽娜 木匠职业者",而是"名字叫 Sabrina,姓氏是 Carpenter 的这个人"。

中文里其实也有点类似,只是你平时不太会直接意识到。很多姓氏追根溯源也和职业、封地、祖源有关,只是现代人不会按字面去理解。

英语母语者看到 Carpenter 作为姓,第一反应也通常是"这是个姓",不是先想"木匠"那个职业含义。

比如:

4月10--12 是 Weekend 1

下一周 4月17--19 是 Weekend 2

这种大型音乐节这样做很常见,本质上是:

同一套 festival,办两轮,阵容大体相近,但现场细节、嘉宾、状态、天气、临时变动可能不同。

英语实际用法里,像这种活动语境,周五晚到周日也经常整个算进 weekend。

Weekend 1 lineup

不是"第一周每天的阵容",而是"第一组周末那三天整体的阵容"。

Weekend 1 Saturday lineup

才是"第一周末里的周六阵容"。

shader loading phase is now correctly set back to PostConfigInit, which should unblock the custom shader in ZMDMirrorSceneFlip.usf. Please compile again and report the next result for the new scene-only path.

"谁持有视口控件"和"谁真正改了最终像素"

AddViewportWidgetForPlayer 文档也直接写了它是在 "Slate viewport's overlay" 上加控件。

断言(Assertion)不是 try...catch 。它们是两种完全不同的程序防御机制:断言用于在开发阶段确立代码的逻辑假设,通常在生产环境中关闭;而 try...catch 用于捕获并处理程序运行时可能出现的异常情况,以维持程序稳定

失败后果 :默认抛出 AssertionError ,通常直接导致程序终止。

try...catch (或 try...except)

  • 目的:处理可预见的外部问题,如文件不存在、网络故障或非法用户输入。
  • 捕获后果:捕获异常后程序可以优雅地修复、记录日志或以其他方式响应,而不会导致程序崩溃。

包含该 shader 类型的模块必须在引擎启动前加载,否则会触发类似"Shader type was loaded after engine init"的断言。官方示例也明确把插件模块的 LoadingPhase 设为 PostConfigInit

把 shader loading phase 改回 PostConfigInit,本质上是在满足"让 ZMDMirrorSceneFlip.usf 对应的 shader 类型尽早注册"这个要求。

这些 shader 的 IMPLEMENT_SHADER_TYPE / IMPLEMENT_GLOBAL_SHADER 所在模块原本默认在 Default 阶段才加载。

引擎已经过了 shader type 注册的安全时机,于是找不到、断言、或者 .usf 没被正确纳入编译流程。

"自定义 shader"其实是:

  • 普通材质里的节点网络,

  • Material Custom 节点里写一点 HLSL,

  • 基于材质系统扩展,而不是自己注册 FGlobalShader / FMaterialShader 类型,

那通常不需要碰模块的 LoadingPhase。因为那不是你自己在 C++ 模块里注册一套新的 shader type,而是走 UE 已有的材质编译管线。

这个案例里需要改,核心不是"用了 .usf 文件"这么简单,而是:

"你在 C++ 模块里引入了一个场景翻转用的自定义全局 shader 路径,必须让该模块在引擎足够早的时候加载并注册 shader type。"

这里的 9:00 和 11:25 默认都是同一个时区,不是不同国家时间。图左下已经写了:

ALL SET TIMES IN PACIFIC TIME

意思是:

所有这些时间都按美国西海岸时间,也就是 Pacific Time(太平洋时间)显示。

9:00 The Strokes

= 中国第二天中午 12:00

11:25 Justin Bieber

= 中国第二天下午 2:25

相关推荐
马士兵教育2 小时前
AI工作岗位的就业分层?
开发语言·人工智能·学习·面试·职场和发展
weixin_443478512 小时前
Flutter学习之第三方组件:视频播放器控件
学习·flutter·音视频
徒 花2 小时前
HCIP学习16 RIP 与 OSPF 路由重分布综合实验
网络·学习·智能路由器·hcip·ensp
landuochong2002 小时前
智能体闭环进展:从学习、记忆、决策到执行
人工智能·学习·claudecode
map1e_zjc2 小时前
Java SpringBoot学习记录(4)
java·开发语言·学习
xian_wwq3 小时前
【学习笔记】3 种零防御 UAC 绕过技术
笔记·学习
●VON3 小时前
【AI工具】本地部署 Dify + Ollama 实现无限 Token 智能体搭建
人工智能·学习·dify·智能体·本地·von
夜瞬3 小时前
NLP学习笔记04:情感分析——从词典方法到 BERT
笔记·学习·自然语言处理
夜瞬3 小时前
NLP学习笔记04:情感分析实践练习实现说明
笔记·学习·自然语言处理