自由学习记录(163)

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 控件被翻转

所以结果就会变成:
你改到的不是"一次性的本次 PIE 图像"
而是"可能被 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

所以更像这样:
先有/创建 SViewport
再创建 FSceneViewport(ViewportClient, SViewport)
然后两者关联起来

而不是这样:
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 框架,里面有 SWidgetSViewport 这种控件。

FViewportClient / UGameViewportClient

是"视口行为的 client",负责这个 viewport 怎么响应输入、怎么画、怎么参与游戏逻辑。

FSceneViewport

是夹在中间的连接对象。官方 API 明确说明它是给 SViewport 用的 viewport,而 UGameViewportClient::GetGameViewport() 返回的就是 FSceneViewport*

SViewport 是一个 Slate 控件,它只负责"在 UI 树里占一个区域,把某个 viewport 显示出来"。

Widget 树参与、
SViewport->SetRenderTransform(...)

改的是这个控件怎么显示。

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 ViewTile ViewTree View 这些。

所以不是"UE 没有 SViewport",而是:

SViewport 不在这个 UMG 设计器控件面板里直接以这个名字给你拖。

Slate

偏原生 C++ UI 框架

类名常见前缀:S

UMG

偏给编辑器/蓝图/UI 设计器使用的上层包装

类名常见前缀:U

Slate

偏原生 C++ UI 框架

类名常见前缀:S

UMG

偏给编辑器/蓝图/UI 设计器使用的上层包装

类名常见前缀:U

在 Widget Blueprint 里通常接触的是:

UImage
UButton
UCanvasPanel
UListView

不是直接接触 SImage
SButton
SViewport

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 蓝图里,BodyFace 都是独立的 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 Class

  • Pawn → 也可以

  • 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" 绑一起,就很容易被包装成:

hoodneighborhood 的口语缩写,但在美国流行文化里,它常常特指 低收入、城市化、常被外界贴上高犯罪或帮派想象标签的社区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,而叫:

  • RuntimeSceneContext

  • LevelRuntimeContext

  • GameInstanceSceneContainer

"这个 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 系:

  • AActor

  • APawn

  • ACharacter

  • ACameraActor

  • ALight

UActorComponent 是:

可以附着在 Actor 上的组件基类。

USceneComponent 继承自 UActorComponent

所以关系是:
C++
UActorComponent
└─ USceneComponent

这意味着:

USceneComponent 先是一个组件,然后额外拥有"空间属性"。

它和普通 UActorComponent 最大的区别是:

它有 Transform,并且能形成父子层级。

还能:

  • AttachToComponent

  • 挂到别的 SceneComponent 下面

  • 带着子组件一起变换

所以 USceneComponent 是 UE 里"场景节点"的基础。
UActorComponent
└─ USceneComponent
├─ UPrimitiveComponent
│ ├─ UStaticMeshComponent
│ ├─ USkeletalMeshComponent
│ ├─ UCameraComponent
│ └─ ...
└─ UAudioComponent

注意这里关键点:

不是所有组件都是 SceneComponent,但所有 SceneComponent 都是 ActorComponent。

RootComponent 不是一个新的"类层级名词",它更像是 AActor 身上的一个重要成员:
C++
USceneComponent* RootComponent;

意思是:

Actor 当前指定的"根场景组件"。

它必须指向一个 USceneComponent 或其子类,不能指向普通 UActorComponent

因为根组件必须承担:

  • 这个 Actor 的整体空间位置

  • 整棵组件树的根节点

  • 其他场景组件往下挂载的起点

所以 RootComponent 的本质是:

Actor 选出来的那个"总根节点"。
MyCharacter
└─ RootComponent = CapsuleComponent
├─ Mesh
└─ CameraBoom
└─ FollowCamera

这里不是说 RootComponent 是一种单独组件类型。

而是说:

在一个 Actor 的多个 SceneComponent 里,会有一个被指定为根,这个指针就叫 RootComponent。

类继承关系

C++
UObject
└─ UActorComponent
└─ USceneComponent

Actor 持有关系

C++
AActor
├─ 持有很 多 UActorComponent
└─ 其中 某个 USceneComponent 被 指定为 RootComponent

Actor 对外暴露空间属性,但这个空间通常是由 RootComponent 承担的。

Actor 的空间操作,底层其实会落到:

  • RootComponent->SetWorldLocation(...)

  • RootComponent->GetComponentTransform()

非常重要的边界:不是所有组件都能做 Root

因为 RootComponent 类型要求是 USceneComponent*

所以:

  • 普通 UActorComponent 不能当根

  • 只有 USceneComponent 或其子类能当根

能当根的:

  • USceneComponent

  • UStaticMeshComponent

  • USkeletalMeshComponent

  • UCapsuleComponent

  • UCameraComponent

  • UAudioComponent

不能当根的:

  • UActorComponent

  • 没有空间属性的逻辑组件

用一个 C++ 例子对齐

比如你自己写一个 Actor:
C++
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()

public:
AMyActor();

protected:
UPROPERTY(VisibleAnywhere)
USceneComponent* MyRoot;

UPROPERTY(VisibleAnywhere)
UAudioComponent* AudioComp;
};

构造函数里:
C++
AMyActor::AMyActor()
{
MyRoot = CreateDefaultSubobject<USceneComponent>(TEXT("MyRoot"));
RootComponent = MyRoot;

AudioComp = CreateDefaultSubobject<UAudioComponent>(TEXT("Audio"));
AudioComp->SetupAttachment(MyRoot);
}


CreateDefaultSubobject

in Unreal Engine C++ is a template function used exclusively within constructor functions (typically AActor constructors) 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"));
Key Characteristics:

  • 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 : Use CreateDefaultSubobject for constructor setup; use NewObject for creating objects at runtime during gameplay.

Common Pitfalls:

  • 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 with CreateDefaultSubobject should generally be declared as UPROPERTY() in the header file to prevent garbage collection and ensure proper serialization, as explained on this Unreal Engine forum thread .
相关推荐
南無忘码至尊2 小时前
Unity学习90天-第2天-认识键盘 / 鼠标输入(PC)并实现WASD 移动,鼠标控制物体转向
学习·unity·c#·游戏开发
sp_fyf_20242 小时前
【大语言模型】OpenVLThinkerV2:面向多领域视觉任务的通用型多模态推理模型
人工智能·深度学习·学习·语言模型·transformer
被考核重击2 小时前
基础算法学习
学习·算法
creator_Li2 小时前
Kafka 全面技术笔记
笔记·学习·kafka
楼田莉子2 小时前
设计模式:构造器模式
开发语言·c++·后端·学习·设计模式
南境十里·墨染春水2 小时前
linux学习进展 进程
linux·运维·学习
sp_fyf_20242 小时前
【大语言模型】 语言模型学习什么以及何时学习?隐式课程假说
人工智能·学习·语言模型
星辰即远方2 小时前
UI学习2
学习·ui
拥抱AGI3 小时前
Qwen3.5开源矩阵震撼发布!从0.8B到397B,不同规模模型性能、显存、速度深度对比与选型指南来了!
人工智能·学习·程序员·开源·大模型·大模型训练·qwen3.5