# 生成对象

# 静态加载

静态加载的方法有:

  • LoadClass:用来加载蓝图类的资源,比如角色蓝图。它加载的资源必须有_C后缀。
  • LoadObject:用来加载非蓝图资源,比如动画,贴图,声音效果,材质等等。它加载的资源不用有_C后缀。
  • StaticLoadObject:用来加载资源文件,比如贴图静态网格等文件。

这里提到的静态加载是需要在构造器中完成的,在运行时动态加载时是值得的,在UE4中FObjectFinder是LoadObject的包装,但是FClassFinder并不是LoadClass的包装,FClassFinder内部调用的是LoadObject。

# 生成Actor

下面都是由蓝图生成Actor。

# 方法一

//===================================头文件================================
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestSpawn.generated.h"

UCLASS()
class TESTPROJECTLEARN1_API ATestSpawn : public AActor
{
    GENERATED_BODY()
    
public:	
    // Sets default values for this actor's properties
    ATestSpawn();
    
    TSubclassOf<AActor> generatedActor;

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:	
    // Called every frame
    virtual void Tick(float DeltaTime) override;

};
//===================================CPP源文件=============================

#include "TestSpawn.h"

// Sets default values
ATestSpawn::ATestSpawn()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    static ConstructorHelpers::FClassFinder<AActor> truckAsset(TEXT("Blueprint'/Game/BP/MyActor.MyActor_C'"));
    //static ConstructorHelpers::FClassFinder<AActor> truckAsset(TEXT("/Game/BP/MyActor"));
    generatedActor = truckAsset.Class;
}

// Called when the game starts or when spawned
void ATestSpawn::BeginPlay()
{
    Super::BeginPlay();

    FVector targetLocation(0, 0, 0);
    FRotator targetRot(0, 0, 0);
    FActorSpawnParameters SpawnInfo;
    AActor* temp = GetWorld()->SpawnActor<AActor>(generatedActor, targetLocation, targetRot, SpawnInfo);
}

// Called every frame
void ATestSpawn::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 方法二

//===================================头文件================================
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestSpawn.generated.h"

UCLASS()
class TESTPROJECTLEARN1_API ATestSpawn : public AActor
{
    GENERATED_BODY()
    
public:	
    // Sets default values for this actor's properties
    ATestSpawn();
    
    TSubclassOf<AActor> generatedActor;
    //UClass* generatedActor;

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:	
    // Called every frame
    virtual void Tick(float DeltaTime) override;

};
//===================================源文件================================
#include "TestSpawn.h"

// Sets default values
ATestSpawn::ATestSpawn()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    static ConstructorHelpers::FObjectFinder<UBlueprint> truckAsset(TEXT("/Game/BP/MyActor"));
    generatedActor = truckAsset.Object->GeneratedClass;
}

// Called when the game starts or when spawned
void ATestSpawn::BeginPlay()
{
    Super::BeginPlay();

    FVector targetLocation(0, 0, 0);
    FRotator targetRot(0, 0, 0);
    FActorSpawnParameters SpawnInfo;
    AActor* temp = GetWorld()->SpawnActor<AActor>(generatedActor, targetLocation, targetRot, SpawnInfo);
}

// Called every frame
void ATestSpawn::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 注意

需要注意以下几点:

  • ConstructorHelpers::FClassFinder中路径有两种写法都可以。
  • FClassFinder<T>的模板名不能直接写UBlueprint,比如FClassFinder<UBlueprint>是不对的,该蓝图的父类是啥,模板名应该写该父类,如果它的父类是Actor,就应该写为FClassFinder<AActor>。
  • 可以把TSubclassOf<T>替换为UClass*。

# 创建UObject对象

如果有UObject的派生类(非Actor、非ActorComponent),那你可以使用NewObject()模板函数来创建其实例对象。

genActor = worldPointer->SpawnActor<AActor>(loc, rot, SpawnInfo);
UStaticMeshComponent* MyMeshComponent = NewObject<UStaticMeshComponent>(genActor, UStaticMeshComponent::StaticClass(), TEXT("MeshComp"));
genActor->SetRootComponent(MyMeshComponent);
USphereComponent* MySphereComponent = NewObject<USphereComponent>(MyMeshComponent, USphereComponent::StaticClass(), TEXT("SphereComp"));
MyMeshComponent->SetStaticMesh(tMesh);
MyMeshComponent->SetVisibility(true);
MyMeshComponent->SetSimulatePhysics(true);
MyMeshComponent->SetEnableGravity(true);

MyMeshComponent->SetCollisionObjectType(ECC_Pawn);
MyMeshComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
MyMeshComponent->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block);
MyMeshComponent->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Block);
MyMeshComponent->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block);

MySphereComponent->SetCollisionObjectType(ECC_Pawn);
MySphereComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
MySphereComponent->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block);
MySphereComponent->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Block);
MyMeshComponent->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block);

//MyMeshComponent->SetWorldScale3D(FVector(0.1, 0.1, 0.1));

MyMeshComponent->RegisterComponent();


genActor->SetActorLocation(loc);
genActor->SetActorRotation(rot);
genActor->SetActorLabel(actorLabel);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

注意:

  • NewObject非构造函数中创建的Box无法及时更新NavMesh,也就是说该box在NavMesh不会被当作障碍物。

# 生成资源(非_C后缀)

# 生成MediaPlayer

//====================头文件=====================
UMediaPlayer* mediaPlayer;
//====================源文件=====================
mediaPlayer = LoadObject<UMediaPlayer>(NULL,TEXT("MediaPlayer'/Game/UI/DisplayVedio.DisplayVedio'"));
1
2
3
4

# 生成datasmith资源


1

# 创建组件

UE4中为Actor创建组件,可使用CreateDefaultSubobject:

MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
MeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
RootComponent = MeshComp;

kernelSphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("kernelSphereComp"));
kernelSphereComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
kernelSphereComp->SetCollisionResponseToAllChannels(ECR_Ignore);
kernelSphereComp->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
kernelSphereComp->SetupAttachment(MeshComp);
1
2
3
4
5
6
7
8
9

注意:

  1. CreateDefaultSubobject必须写在Actor的无参构造函数中,否则crash。
  2. CreateDefaultSubobject中的TEXT或者FName参数在同一个Actor中不能重复,否则crash。

# 加载资源

在UE4中,项目中的所有资源文件,不要看做是文件,而要理解为"静态对象",也就是对象序列化的产物。加载项目资源可以使用"UObject::StaticLoadObject()"函数,其中重要的参数为对象的Name,而不是文件路径。UE底层提供文件读取功能,无论资源文件是存储到独立的.uasset文件,还是存储到.PAK文件中,对于上层都不需要关心。

UStaticMesh* SM_Vase = Cast<UStaticMesh>(StaticLoadObject(UStaticMesh::StaticClass(),  
    NULL,  
    TEXT("/Game/Assets/StaticMeshes/SM_Vase"))  
    );  
 
StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComponent"));  
StaticMeshComponent->SetStaticMesh(SM_Vase);
1
2
3
4
5
6
7

# 总结

UE4中的对象模型是基于对象原型的,很像JavaScript,UClass被认为是和UObject相关类的默认实例,被称为Class Default Object(CDO),他会在引擎初始化时通过类构造器被分配空间,然后构造一次。CDO可被看作一个模板,其他类的实例可从该模板借鉴,但是构造器不会再被调用了。

这意味着类构造器不能包含任何运行时逻辑,只应该用来初始化CDO和它的属性,如果类包含任何子对象,像actor组件,这些组件也是同样的,它们自己默认的对象也必须得先被构建。对象的真实实例肯定会被延期,得在引擎初始化后。所以每次一个类的新实例被Gameplay代码创建,它的父类以及子类都得从它们对应的类实例化。

下面这些方法是用来处理对象在不同的场景下被创建时应用的:

  • UObject::CreateDefaultSubobject只在类的构造器中可调用,被用来实例化CDO及其子类的对象,设置它的外部类为其他对象调用它时的调用对象,当它的类被实例化时被创建的对象会成为属性的默认对象。
  • NewObject<T>是被用来在Gameplay逻辑中引擎初始化后正常实例化对象的方法,它提供几种便于使用的重载来处理大部分场景。
  • UWorld::SpawnActor<T>便于生成关卡中的Actors,可以指定location和rotation,spawn碰撞设置,它确保生成的是actor类外,同NewObject<AActor>一样。
  • ConstructObject因为NewObject被移除了。

关于对象实例化可以在引擎CoreUObject模块中UObject/UObjectGlobal.h来查看,这些函数内部最终会调用StaticConstructObject_Internal,该函数会处理真实对象的创建。

参考资料:

# SpawnActor和NewObject

SpawnActor被用来在当前的UWorld中基于UObject创建AActor,它内部会调用NewObject来创建指定的AActor,但是还会安装Transform,添加到当前关卡的Actors数组中,也会帮助创建Actor的组件,初始化蓝图相关的构造脚本等。