组件与世界

自动关联目录:组件与世界

组件是 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 独立于 ActorActor 不复制,组件复制了也没用
组件 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 链过长会放大变换计算与复制开销