组件与世界
自动关联目录:组件与世界
组件是 UE 的组合机制:Actor 提供"在世界里存在"的能力,组件提供"具体能做什么"。而 World / Level 是 Actor 存在的容器。
一句话定位:Actor 是容器与网络身份,组件是功能与世界变换;能不能拿到 World 决定了你能不能访问游戏状态。
组件体系
UActorComponent (功能:无变换)
└── USceneComponent (有 Transform,可挂载形成树)
└── UPrimitiveComponent (有渲染/碰撞体)
└── UMeshComponent / USkeletalMeshComponent / UStaticMeshComponent| 类 | 什么时候用 |
|---|---|
UActorComponent | 纯逻辑:血量、背包、技能(不需要位置) |
USceneComponent | 需要位置:挂点、武器槽、相机臂 |
UPrimitiveComponent | 需要渲染或碰撞 |
判断标准很简单:这个东西有没有位置?没有 → UActorComponent。有 → USceneComponent。需要被看到/碰撞 → UPrimitiveComponent。
选错的代价:UActorComponent 挂不了子物体;UPrimitiveComponent 会参与碰撞查询与渲染,纯逻辑用它白白增加开销。
创建与注册
// 构造函数里:默认子对象,编辑器可见
Mesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mesh"));
Mesh->SetupAttachment(RootComponent); // 挂到根
// 运行时:手动创建
UMyComp* C = NewObject<UMyComp>(this);
C->RegisterComponent(); // 必须,否则不 Tick、不复制
C->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);| 要点 | 说明 |
|---|---|
CreateDefaultSubobject | 只能在构造函数里(见 UObject) |
RegisterComponent() | 运行时手动创建必须调,否则完全不工作 |
SetupAttachment vs AttachToComponent | 前者在构造函数里,后者在运行时 |
SetIsReplicated(true) | 组件要复制时单独设置 |
运行时创建组件忘记 RegisterComponent 的表现:不 Tick、不渲染、不复制,但对象存在——非常难查。
组件复制
UMyComp::UMyComp()
{
SetIsReplicatedByDefault(true); // 或运行时 SetIsReplicated(true)
}| 要点 | 说明 |
|---|---|
组件的 bReplicates 独立于 Actor | Actor 不复制,组件复制了也没用 |
| 组件 RPC | 支持,走所属 Actor 的连接(见 RPC) |
| 组件属性复制 | 在组件自己的 GetLifetimeReplicatedProps 里注册 |
SetNetAddressable | 动态组件需要,否则无法被引用 |
查找组件
// 缓存引用(推荐)
UPROPERTY()
UMyComp* Cached;
// 按类查找(有开销)
UMyComp* C = FindComponentByClass<UMyComp>();
TArray<UMyComp*> All;
GetComponents<UMyComp>(All);FindComponentByClass 每次都遍历数组,热路径上应该缓存指针或用 UPROPERTY 引用。
World / Level 层次
UGameInstance(跨关卡,进程级)
└── UWorld(一个世界)
├── PersistentLevel(持久关卡)
├── StreamingLevel 1..N(子关卡)
└── 所有 Actor| 概念 | 说明 |
|---|---|
UWorld | 一个可运行的场景集合,切关卡时会换(或复用) |
ULevel | 一个关卡,包含其 Actor |
PersistentLevel | 永远存在的主关卡 |
| Streaming Level | 按需加载/卸载的子关卡 |
UGameInstance | 跨关卡存活,放全局服务(见 Subsystem) |
UWorldSubsystem 的生命周期跟随 World,UGameInstanceSubsystem 跟随 GameInstance——选错了就会出现"切场景后数据没了"。
GetWorld 的坑
UWorld* W = GetWorld(); // UObject 的虚函数| 场景 | GetWorld() 返回 |
|---|---|
| 已 Spawn 的 Actor / ActorComponent | 有效 |
| CDO、未 Spawn 的对象 | null |
UActorComponent(非 SceneComponent) | 通过 Owner 拿,Owner 无效则 null |
| 编辑器里的资产对象 | null 或编辑器 World |
UGameInstanceSubsystem | 用 GetGameInstance()->GetWorld() |
"GetWorld 返回 null" 的前三名原因:在构造函数里调、在 CDO 上调、对未注册的对象调。
世界级服务放哪
| 需求 | 放哪 |
|---|---|
| 跨关卡的账号、存档、匹配 | UGameInstanceSubsystem |
| 只在当前关卡有效的系统(刷怪、天气) | UWorldSubsystem |
| 玩家相关(输入、UI 状态) | ULocalPlayerSubsystem |
| 全局配置 | UEngineSubsystem |
不要用静态单例(理由见 模块插件与 Subsystem)。
常见坑
| 坑 | 说明 |
|---|---|
运行时创建组件不 RegisterComponent | 完全不工作但不报错 |
构造函数里用 AttachToComponent | 应用 SetupAttachment |
纯逻辑用 UPrimitiveComponent | 多余的碰撞/渲染开销 |
热路径反复 FindComponentByClass | 应缓存 |
| Actor 不复制但组件设了复制 | 无效 |
在 CDO / 构造函数里 GetWorld() | null |
| 关卡级数据放进 GameInstance | 切场景不清理,越积越多 |
| 跨关卡数据放进 World | 切场景就丢 |
| 组件树挂太深 | Attach 链过长会放大变换计算与复制开销 |
许可协议:CC BY
作者:Davids
本文链接:https://hustjjd.github.io/948fe813.html
更新于:2026年10月10日