news 2026/5/9 6:57:16

自由学习记录(163)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自由学习记录(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 (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"));

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:UseCreateDefaultSubobjectfor constructor setup; useNewObjectfor 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.
  • MissingUPROPERTY():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 .
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 9:11:17

3步精通ImStudio:Dear ImGui开发者的实时布局设计神器

3步精通ImStudio&#xff1a;Dear ImGui开发者的实时布局设计神器 【免费下载链接】ImStudio GUI layout designer for Dear ImGui 项目地址: https://gitcode.com/gh_mirrors/im/ImStudio ImStudio是一款专为Dear ImGui框架设计的实时GUI布局创建与编辑工具&#xff0c…

作者头像 李华
网站建设 2026/4/17 21:09:58

告别Vivado依赖!纯Modelsim 2022.4搭建计数器仿真环境(附常见错误修复)

纯Modelsim 2022.4搭建高效计数器仿真环境全指南 在FPGA和数字IC设计领域&#xff0c;仿真验证是确保设计功能正确的关键环节。虽然Vivado、ISE等集成开发环境提供了完整的仿真工具链&#xff0c;但对于专注于RTL验证的工程师来说&#xff0c;独立使用Modelsim进行轻量化仿真不…

作者头像 李华
网站建设 2026/4/17 19:47:29

WuliArt Qwen-Image Turbo快速体验:输入提示词,几秒获得1024高清大作

WuliArt Qwen-Image Turbo快速体验&#xff1a;输入提示词&#xff0c;几秒获得1024高清大作 1. 引言&#xff1a;从想法到画面的极速通道 你有没有过这样的经历&#xff1f;脑子里突然冒出一个绝妙的画面&#xff0c;想把它变成一张图片&#xff0c;结果要么被复杂的AI绘画工…

作者头像 李华
网站建设 2026/4/17 17:42:12

3个关键步骤掌握IDR:Delphi逆向工程的高效实战指南

3个关键步骤掌握IDR&#xff1a;Delphi逆向工程的高效实战指南 【免费下载链接】IDR Interactive Delphi Reconstructor 项目地址: https://gitcode.com/gh_mirrors/id/IDR 在Windows平台下&#xff0c;Delphi编写的应用程序广泛存在于企业系统、遗留软件甚至安全威胁中…

作者头像 李华