C++ 里写的那种后处理 pass,比如基于FGlobalShader、Render Graph、fullscreen pass、compute shader,然后.usf里自己定义 shader class 并注册,那就很可能需要让模块早加载,常见就是PostConfigInit。因为 UE 官方对 Global Shader 的要求就是:声明这些 shader type 的模块必须在引擎初始化早期加载,否则会出现 shader type 注册过晚的问题。官方文档和插件示例都把这类模块放在PostConfigInit。
后处理材质
Post Process Material材质编辑器里的 Custom 节点
基于现有材质系统做屏幕后效
那通常不需要改模块加载阶段。因为这时你没有自己注册新的 Global Shader 类型,而是在用 UE 已经存在的材质/后处理框架。
底层渲染路线,自己写.usf+FGlobalShader/ compute / RDG pass:通常要考虑早加载,经常就要改成PostConfigInit。
残留的是 widget transform 状态,而不是某一帧场景像素。
SetRenderTransform()改的是控件状态,控件如果被复用,或者退出 PIE 时没有被你完整恢复,那么状态就会继续留在 Editor 那边。这里的污染对象不是 World,不是某一帧 Render Pass,而是 Slate widget tree 里的状态。
通过UGameViewportClient去改SViewport的 render transform”这个切入方式不对
这种做法落点在 Slate 层
自然会带来 Editor/PIE 共享状态问题
改的是共享/可复用的 Slate 视口状态,PIE 退出后残留到 Editor 就完全说得通。
对场景图像做一次真正的屏幕空间翻转,再让 UI 按正常方向叠加上去。
这和“直接把SViewport整体 Scale X = -1”不是一回事。前者是图像处理,后者是控件变换。
“场景渲染完成后、Slate 合成 UI 之前”是你想要的结果描述,但在 UE 里真正落地时,往往不会直接以“我就在这两者中间插一刀”的抽象形式暴露给你。工程上更常见的实现思路是:
要么走后处理/屏幕空间材质/渲染管线节点,
要么单独把 3D 画到一个目标里,再在 UI 层按你想要的方式显示。
而不是去改共享的SViewport变换。
这条链的含义是:
场景渲染结果被翻转
而是:
承载场景画面的 Slate 控件被翻转
所以结果就会变成:
而是“可能被 Editor / PIE 复用的视口控件状态”
UGameViewportClient
是“视口的管理/控制对象”。
SViewport
是“视口在 Slate 里的显示控件”。
UGameViewportClient
└─ 持有/管理 viewport 相关逻辑
└─ 对接 FViewport / FSceneViewport
└─ FSceneViewport 被 SViewport 这个 Slate 控件拿来显示
engine 面向 game viewport 的接口/管理层。
UGameViewportClient
FSceneViewport
是“真正的 scene viewport 对象”
“A viewport for use with Slate SViewport widgets.”
FSceneViewport
Slate 控件SViewport的viewport 后端
FSceneViewport则是专门给SViewport这类 Slate 视口控件使用的 viewport。
官方构造函数就已经把关系写出来了:
FSceneViewport(FViewportClient* InViewportClient, TSharedPtr<SViewport> InViewportWidget)。
这说明FSceneViewport在构造时,需要拿到一个SViewport指针;也就是它知道“自己对应哪个 Slate 视口控件”,而不是SViewport自己内部去new它。Epic Games Developers
所以更像这样:
再创建 FSceneViewport(ViewportClient, SViewport)
然后两者关联起来
而不是这样:
new FSceneViewport(...)
SViewport
是显示壳。
FSceneViewport
是和这个显示壳绑定的视口对象。
It was hard because this was not one bug. It was a chain of architectural mismatch, Unreal shader/module loading rules, hidden activation, and then shader/pipeline-level failure after registration was already working. Once those were separated, the problem became tractable.
SViewport
= Slate 层的“显示控件”
FSceneViewport
= 被这个控件显示/承载的 viewport 对象
UGameViewportClient
= 管这个 viewport 行为的 client / 管理层
UGameViewportClient / FViewportClient
↓
FSceneViewport
↓
SViewport
如果只看“UI 显示层级”,确实是SViewport在最外面,因为它是控件壳;
但如果看“谁是 viewport 本体”,那反而是FSceneViewport更像本体对象,SViewport只是把它显示出来。
Slate
是 UI 框架,里面有SWidget、SViewport这种控件。
FViewportClient / UGameViewportClient
是“视口行为的 client”,负责这个 viewport 怎么响应输入、怎么画、怎么参与游戏逻辑。
FSceneViewport
是夹在中间的连接对象。官方 API 明确说明它是给SViewport用的 viewport,而UGameViewportClient::GetGameViewport()返回的就是FSceneViewport*。
SViewport是一个 Slate 控件,它只负责“在 UI 树里占一个区域,把某个 viewport 显示出来”。
Widget 树参与、
改的是这个控件怎么显示。
SViewport前面的S,通常表示它是Slate widget。
Widget
是UI 控件这个大类概念。
按钮、文本、边框、图片、面板、视口控件,都可以是 widget。
Viewport
是视口/显示一块渲染内容的区域这个语义概念。
它强调的是“这里有一块区域,用来看场景、接收视角、显示渲染结果”。
就像:
SButton
是 widget 里的“按钮控件”
STextBlock
是 widget 里的“文本控件”
SViewport
是 widget 里的“视口控件”
Slate 是底层框架,而 UMG 是建立在 Slate 之上的可视化封装层。
- 不依赖平台:它直接在渲染层绘图,不依赖于 Windows 或 Android 等系统的原生控件。
- 应用场景:通常用于开发编辑器扩展、复杂的 C++ 插件,或者对性能要求极高的底层 UI 逻辑。
UMG 全称 Unreal Motion Graphics UI Designer,是为开发者提供的一个可视化设计工具。
- 可视化编辑:允许美术和设计师在编辑器里通过拖拽(Widget Blueprint)来布局 UI、设置属性和制作动画。
- 蓝图支持:UMG 完美支持蓝图系统,不需要写 C++ 就能实现复杂的 UI 逻辑。
UMG/Widget Blueprint 可直接拖拽使用的控件,
比如List View、Tile View、Tree View这些。
所以不是“UE 没有SViewport”,而是:
SViewport不在这个 UMG 设计器控件面板里直接以这个名字给你拖。
Slate
偏原生 C++ UI 框架
类名常见前缀:S
UMG
偏给编辑器/蓝图/UI 设计器使用的上层包装
类名常见前缀:U
Slate
偏原生 C++ UI 框架
类名常见前缀:S
UMG
偏给编辑器/蓝图/UI 设计器使用的上层包装
类名常见前缀:U
在 Widget Blueprint 里通常接触的是:
UImageUButtonUCanvasPanelUListView
不是直接接触SImageSButtonSViewport
Camera
最基础的相机组件。能作为角色视角、跟随相机、第一人称/第三人称摄像机使用。
你现在要“挂到头骨/眼睛附近并跟着动”,优先就是它。
Cine Camera
电影摄影机。给 Sequencer、镜头语言、焦距、光圈、胶片宽高、对焦这些更专业的影视参数用。
不是不能用来跟角色头部,但它偏“拍摄镜头”,不是常规玩家视角组件。
做 MetaHuman 展示、过场、虚拟制片时常用。
Gameplay Camera
这一般不是“一个普通相机组件替代品”的思路,它属于 UE 新的 gameplay camera 系统相关内容,偏相机逻辑框架、模式切换、摄像机行为管理。
如果你现在还在做“把相机固定到头上”,先别碰它,复杂度高,而且不是入门路径。
Gameplay Camera Parameter Setter
顾名思义,更像给 Gameplay Camera 系统改参数的辅助组件,不是你直接挂来当视角的。
Gameplay Camera Rig
也是 Gameplay Camera 框架里的 rig 资产/逻辑概念,不是“我想加一个相机就选这个”。
Camera Shake Source
这是震动源。比如爆炸点、冲击源,让附近相机产生 shake。
它本身不是你的主视角相机。
MetaHuman 蓝图里,Body和Face都是独立的 Skeletal Mesh Component。
而你想让相机跟着“脸/头”走,更合适的是把相机挂在Face下面,不是随便挂在最外层Root,也不是优先挂在Body。
虚幻引擎的渲染系统总是通过PlayerController寻找一个ViewTarget(视图目标)。
- 如果你运行游戏时,PlayerController自动占有(Possess)了包含该摄像机组件的角色(Pawn/Character),那么引擎会自动寻找该 Actor 内部激活的摄像机组件作为默认视角。
- 结论:如果这是你角色蓝图中唯一的摄像机,且你正常控制着这个角色,那么它就是默认摄像机。
2. “自动激活”属性 (Auto Activate)
每个摄像机组件都有一个Auto Activate属性:
- 在摄像机组件的Details(细节)面板中搜索
Auto Activate。 - 默认情况下它是勾选的。如果取消勾选,即使你进入了该角色,引擎也可能找不到有效的摄像机,从而退回到角色的中心位置(坐标 0,0,0)进行观察。
World Settings这里虽然是空的,但这不代表没有 GameMode。
它只是表示:这个关卡没有单独 override,实际会回退到 Project Settings 里的默认 GameMode。
也就是说,你现在真正生效的,大概率是:
Edit -> Project Settings -> Maps & Modes
里面的:
Default GameMode
Default Pawn Class
如果那里还是默认值,PIE 时依然会生成默认 Pawn,所以你前面看到DefaultPawn0很正常。
默认的 Spectator Pawn
GameMode 不是“直接负责玩家输入”的核心本体,它更像是“这张关卡的规则配置中心”:
它决定默认用哪个 Pawn、哪个 PlayerController、HUD、GameState、是否能重生之类。
真正和“视野能不能动”更直接相关的
PlayerController
你现在这张图里,Outliner 里那个BP_NewMeta...nCharacter我注意到类型列已经像是在说它是某个蓝图类,但关键还是要确认它蓝图父类到底是不是Character。
只有是Character/Pawn系,才应该能进这个列表。
如果父类是:
Character→ 可以作为 Default Pawn ClassPawn→ 也可以Actor→ 不会出现在这里
Possess 了这个 Character
Character 当前是否真的把这台CineCamera当作视角
Character:Pawn 的一个带行走系统的常用子类,适合人形角色
PlayerController
玩家的意志 / 输入中枢。
PlayerController Possess 一个 Pawn
然后 Pawn 再执行移动、跳跃、旋转等行为。
所以链上关系是:
PlayerController -> Possess -> Pawn/Character
没有 Possess,这个 Pawn 就只是站在场景里的一坨对象,不会响应你的玩家输入。
只要一个 Actor 想被玩家或 AI 占有,通常它应该是 Pawn 或其子类。
PlayerController 当前 ViewTarget 指向谁,以及这个 Actor 上哪个 CameraComponent 在输出。
是 “in the hood” 这个词本身带很强的语境。
这里不是单纯“在社区里”,而是带一种美国流行文化里对“街区 / 治安复杂 / 强势地盘文化”的想象。视频标题一旦把明星和 “in the hood” 绑一起,就很容易被包装成:
hood是neighborhood的口语缩写,但在美国流行文化里,它常常特指低收入、城市化、常被外界贴上高犯罪或帮派想象标签的社区。Dictionary.com 就把它解释成“尤其指由低社会经济地位的非裔美国人居住的城市社区”;而slum则更偏向“住房条件恶劣、拥挤、基础设施差”的“贫民窟”概念,这两个词并不完全等同。
CapsuleComponent
不能随便删。
这是CharacterMovementComponent、碰撞、导航、地面检测这一整套的根基。Character之所以叫Character,很大程度上就是因为它默认按“胶囊体 + 角色移动组件”来工作。
你如果真不想要 Capsule,通常不是“删掉它”,而是说明你不该继承 Character,而该继承:
Pawn
或者干脆Actor
对Character蓝图来说,Capsule 基本视为必备。
你当然可以把显示模型放到别的 SkeletalMeshComponent 上,但那样会和 Character 生态有点“别扭”:
很多教程、很多节点、很多默认逻辑都默认你在用这个Mesh。
ArrowComponent
这个相对特殊。
它更多是编辑器里的方向辅助,不是 Character 运行必须核心。
很多时候它是可以不管的,有时也能删,但是否允许删取决于它是不是父类默认子对象、以及当前蓝图/父类如何声明它。
在大多数实际项目里,结论很简单:
不需要特地删,留着也几乎没成本。
它主要是给你在编辑器里看“角色前方朝哪”。
在 UE 里,Possess不是“处理”那个意思,它是很明确的游戏框架术语:
“控制权接管” / “玩家控制器占有某个 Pawn”
更准确地说:
PlayerController -> Possess -> Pawn/Character
意思是:
某个PlayerController开始控制某个Pawn(或 Character)。
GameSession:多人/会话相关的管理对象。即使你没做联网,框架也可能顺手建。
PlayerCameraManager:真正管理当前玩家视角相机的对象。
不需要 HUD,就在你的 GameMode 里把 HUD Class 设成 None。
如果你不需要自定义 PlayerState,就用最基础的默认类。
PlayerState:玩家状态数据。
AActor是可放置/可生成到关卡里的基础对象,APawn是可被 Controller 控制的 Actor,ACharacter是带角色运动与碰撞等默认能力的 Pawn,APlayerController负责人类玩家控制,AGameModeBase在地图初始化时被实例化并决定本局规则与默认类;这些都还是 UE 5.7 官方文档当前明确保留的主干概念。UWorld依然是地图/沙盒的顶层对象,UGameInstance依然是一次运行实例级别的高层管理对象。也就是说,真正的“骨架”至少还包括UObject / UWorld / UGameInstance / UActorComponent / USceneComponent这一整层,不是只靠那 5 个类撑起来。
“世界里有 Actor,Actor 挂 Component,玩家通过 Controller/Pawn 体系控制对象,运行期有 World/GameInstance 这样的上下文层”,这些属于 UE 的根本 ontology,改了等于把几乎全部编辑器、蓝图、序列化、网络复制、文档和生态一起掀翻,所以它们通常很稳。
蓝图这个东西,在磁盘和 Content Browser 里,以.uasset的形式存在,所以它是一个资产。
但它里面承载的语义,往往是“一个类定义”。
UWorld主要管这些事:
第一,持有这次运行里的 Actor 集合
也就是这个关卡里现在有哪些 Actor,它们的生成、销毁、遍历,都要经过这层上下文。
第二,提供 Spawn / Destroy 的场所
你不是在真空里SpawnActor,而是在某个 World 里生成一个 Actor。
因为生成到哪个运行中的场景里,这事必须有归属。
如果一开始不叫World,而叫:
RuntimeSceneContextLevelRuntimeContextGameInstanceSceneContainer
“这个 NPC 血量是多少”
这是 Actor 的事。
“当前关卡里有哪些 Actor”
这是 World 的事。
“这个角色要不要播放动画”
这是 Actor / Component 的事。
“把一个新 Actor 生到当前正在运行的这张地图里”
这是 World 的事。
“这个场景这一帧过了多少时间”
这是 World 的事。
“当前是不是 PIE 运行世界”
这是 World 上下文的事。
.umap地图资产
→ 加载后会对应一个UWorld以及里面的 Level / Actor 等运行时结构
蓝图类本身,约等于一个UE 类的可视化定义。
里面的Component,更像是“挂在这个对象上的子对象/子模块”,不只是普通成员变量。UActorComponent官方定义就是可复用行为模块;有变换的是SceneComponent,可渲染的是PrimitiveComponent。
在 UE 里,Actor 本身通常不直接持有“可挂接、可层级传播”的场景变换实现;真正的世界变换是靠 RootComponent,也就是某个 SceneComponent 来承载的。
UE 就不想让 Actor 本体直接承担完整的场景节点职责
于是“空间性”被下放到
SceneComponent
AActor自己不是直接保存完整空间层级的那个“组件树节点”。
真正带有Transform、能挂子组件、形成层级关系的,是USceneComponent这一类组件。
Unreal Engine 中的 Actor Default Scene Root 是一個基礎的 Scene Component ,用作 Actor 的根節點 。它為無視覺表示的 Actor 提供 transform 資料(位置、旋轉、縮放)。這在組合物件時至關重要,可作為容器以附加 Static Mesh、Camera 或粒子系統,若移除此根節點,Actor 則可能無法在場景中定位。
USceneComponent是“带空间层级的组件”
本质都还是 Actor 系:
AActorAPawnACharacterACameraActorALight
UActorComponent是:
可以附着在 Actor 上的组件基类。
USceneComponent继承自UActorComponent。
所以关系是:
└─ USceneComponent
这意味着:
USceneComponent先是一个组件,然后额外拥有“空间属性”。
它和普通UActorComponent最大的区别是:
它有Transform,并且能形成父子层级。
还能:
AttachToComponent挂到别的
SceneComponent下面带着子组件一起变换
所以USceneComponent是 UE 里“场景节点”的基础。
└─ USceneComponent
├─ UPrimitiveComponent
│ ├─ UStaticMeshComponent
│ ├─ USkeletalMeshComponent
│ ├─ UCameraComponent
│ └─ ...
└─ UAudioComponent
注意这里关键点:
不是所有组件都是 SceneComponent,但所有 SceneComponent 都是 ActorComponent。
RootComponent不是一个新的“类层级名词”,它更像是AActor身上的一个重要成员:
意思是:
Actor 当前指定的“根场景组件”。
它必须指向一个USceneComponent或其子类,不能指向普通UActorComponent。
因为根组件必须承担:
这个 Actor 的整体空间位置
整棵组件树的根节点
其他场景组件往下挂载的起点
所以RootComponent的本质是:
Actor 选出来的那个“总根节点”。
└─RootComponent = CapsuleComponent
├─ Mesh
└─ CameraBoom
└─ FollowCamera
这里不是说RootComponent是一种单独组件类型。
而是说:
在一个 Actor 的多个 SceneComponent 里,会有一个被指定为根,这个指针就叫 RootComponent。
类继承关系
└─UActorComponent
└─USceneComponent
Actor 持有关系
├─ 持有很多UActorComponent
└─ 其中某个USceneComponent 被指定为RootComponent
Actor 对外暴露空间属性,但这个空间通常是由RootComponent承担的。
Actor 的空间操作,底层其实会落到:
RootComponent->SetWorldLocation(...)RootComponent->GetComponentTransform()
非常重要的边界:不是所有组件都能做 Root
因为RootComponent类型要求是USceneComponent*。
所以:
普通
UActorComponent不能当根只有
USceneComponent或其子类能当根
能当根的:
USceneComponentUStaticMeshComponentUSkeletalMeshComponentUCapsuleComponentUCameraComponentUAudioComponent
不能当根的:
纯
UActorComponent没有空间属性的逻辑组件
用一个 C++ 例子对齐
比如你自己写一个 Actor:
class AMyActor : public AActor
{
GENERATED_BODY()
public:
AMyActor();
protected:
UPROPERTY(VisibleAnywhere)
USceneComponent* MyRoot;
UPROPERTY(VisibleAnywhere)
UAudioComponent* AudioComp;
};
构造函数里:
{
MyRoot = CreateDefaultSubobject<USceneComponent>(TEXT("MyRoot"));
RootComponent = MyRoot;
AudioComp = CreateDefaultSubobject<UAudioComponent>(TEXT("Audio"));
AudioComp->SetupAttachment(MyRoot);
}
CreateDefaultSubobjectin Unreal Engine C++ is a template function used exclusively within constructor functions (typicallyAActorconstructors) to instantiate components, ensuring they are editable in the editor and blueprint-derived classes. It defines default components, such as meshes or cameras, that belong to an actor's Class Default Object (CDO).
// Example: Creating a Scene Component in an Actor Constructor MyComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
- Usage Context:Only used within class constructors to create default subobjects.
- Editor Integration:Components created this way are fully editable in the Blueprint Editor and the level editor.
- Syntax:
Type* Variable = CreateDefaultSubobject<Type>(TEXT("UniqueName"));. - Vs.
NewObject:UseCreateDefaultSubobjectfor constructor setup; useNewObjectfor creating objects at runtime during gameplay.
- Duplicate Names:If a component is created with a name that already exists in the hierarchy, Unreal will crash. Ensure unique names, particularly in complex inheritance structures.
- Missing
UPROPERTY():Subobjects created withCreateDefaultSubobjectshould generally be declared asUPROPERTY()in the header file to prevent garbage collection and ensure proper serialization, as explained on this Unreal Engine forum thread .