Tobe or not tobe
单词本:
dangling:空的,悬挂的
A dangling pointer (pointer referring to something that has been removed from memory) is an example of a bug that is hard to track if it occurs.
contiguous:邻接的
malloc Allocates a zone of contiguous space for use
data conglomerate:数据联合体
A package (UPackage) in UE4 is just a data conglomerate.
这本书里推荐了两本书:设计模式:可复用面向对象软件的基础 和Game Development Patterns and Best Practices
另外,项目上传到Git上究竟该上传些什么才合适:观察书中配套资源就可以发现
图一 github上应该放置的
图二 本地编译的文件目录
,相对于编译后2个G的工程文件,注意去掉StarterContent,可以直接使用书中配套的.gitignore文件,上传至github上的大小也就几十K左右,十分合适工程的管理。
Chapter03:Memory Management, Smart Pointers, and Debugging
这章节的内容不仅适用于UE4本身,在智能指针的使用上和软件调试上提供可以借鉴的经验。新版本此章节和第一版基本相同
在UE4中未经管理的内存处理:
使用malloc和free,或者是new和delete。两年前我还是能够玩转malloc和free的。如果尝试访问一块未经分配的内存空间会产生Segement fault。
使用引擎提供的:
对UObject的创建使用NewObject或者ConstructObject,其中NewObject相比于ConstructObject来说参数更少,使用起来更简单。在引用计数之前显示调用UObject::ConditionalBeginDestroy()销毁UObject对象。 对Actor类型使用SpawnActor实例化。
// Create an object
UAction * action = NewObject<UAction>(GetTransientPackage(),
UAction::StaticClass() /* RF_* flags */ );
// Destroy an object
action->ConditionalBeginDestroy();
使用UE4支持的智能指针:TSharedPtr,TWeakPtr,TAutoPtr
TsharedPtr是线程安全的,TAutoPtr是线程不安全的,相应智能指针的学习需要参考 https://docs.unrealengine.com/en-us/Programming/UnrealArchitecture/SmartPointerLibrary。书中简要的给出了关于智能指针使用的注意点,如下图:
//对于一个没有继承自UObject的类,使用智能指针创建
class MyClass { };
TSharedPtr<MyClass>sharedPtr( new MyClass() );
//智能指针提供了IsValid属性方法来判断是否为空指针
if(sharedPtr.IsValid()){...}
//使用智能指针的好处在于系统会自动释放指针指向的内存空间
UE4的GC和UPROPERTY
注意:用TArray存储一系列对象的时候,需要把这一系列对象声明为UPROPERTY的(即使用户并不想用蓝图来编辑),否则会出现内存错误
强制GC:
使用场景是在内存容量过大,用户想要通知GC来清理的时候,对于继承自UObject类型的对象,显示调用GetWorld()->ForceGarbageCollection( true ); //这里GetWorld() VS编译器会爆红,但编译通过,也可以添加头文件“Engine/World.h”
Debug代码这一块暂时不熟练。这一块先保留
Chapter05:Actors and Components
在对第一版本代码进行操练的时候发现有些代码已经过时了,此章节看看进行了那些改进
书中提到,UE4提供了一些Actor类可能在实际项目中并不能够满足用户的需求,这个时候可以通过合成或继承的方式自定义一些Actor。
自定义Actor,创建一个MyFirstActor类,
下面是类的内容
// MyFirstActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyFirstActor.generated.h"
UCLASS()
class CHAPTER_04_API AMyFirstActor : public AActor
{
GENERATED_BODY()
public:
AMyFirstActor();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
};
// MyFirstActor.cpp
#include "MyFirstActor.h"
AMyFirstActor::AMyFirstActor()
{
PrimaryActorTick.bCanEverTick = true;
}
void AMyFirstActor::BeginPlay()
{
Super::BeginPlay();
}
void AMyFirstActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
下面针对MyFirstActor做一些说明
首先是与旧版的改变:UE4.15之前,并没有CoreMinial.h,反而是一种Engine.h或者是EngineMinimal.h
的形式。另外就是需要添加一堆头文件,用到的时候会记录需要添加的头文件。
UHT是Unreal的头文件工具,它会自动的生成一些辅助的宏标记。这里的.generated.h就是用于UHT工作的。
另外和UHT对应的还有GENERATED_BODY(),它会自动的为用户添加代码运行与构建所需的宏。
class CHAPTER_04_API AMyFirstActor : public AActor
其中CHAPTER_04为工程名,MyFirstActor为类名,类名前面加A表明是一种Actor的子类
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
虚函数重写,需要Super::调用上级代码
PrimaryActorTick.bCanEverTick = true;使得调用Tick函数
上面都是UE4默认提供好的
对于一个裸奔的Actor,就可以写成如下类型:
只包含构造函数和BeginPlay函数,其他的按需添加。
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyFirstActor.generated.h"
UCLASS()
class CHAPTER_04_API AMyFirstActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyFirstActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
};
在GameMode中实例化MyFirstActor:
创建基于GameModeBase的类,
//UECookbookModeBase.h
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "UECookbookModeBase.generated.h"
UCLASS()
class CHAPTER_04_API AUECookbookModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
virtual void BeginPlay()override;
};
//UECookbookModeBase.Cpp
#include "UECookbookModeBase.h"
#include "MyFirstActor.h"
//#include "Engine.h"
void AUECookbookModeBase::BeginPlay()
{
Super::BeginPlay();
GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, TEXT("Actor Spawning"));
//GEngine会爆红,但编译通过,也可以添加 "Engine.h",使得编译器识别
FTransform location;
GetWorld()->SpawnActor<AMyFirstActor>(AMyFirstActor::StaticClass(), location);
}
针对UECookbookModeBase的说明
FTransform location会初始化一个全0的Transform;
对于Actor类的实例化,需要使用GetWorld()->SpawnActor<类名>()的方式创建
GameMode会由引擎运行时创建。需要在WorldSetting中指定
书中为了简化说明,给了编译时绑定的方案,那么怎么样和UObject那一节一样,动态的绑定类型呢?一样的可以使用UPROPERTY指定一个TSubClassOf<C++类型名> ItemClass来存储类名,正如我上一篇所记录的那样:
创建UFUNCTION
所有的函数都可以指定为UFUNCTION,UFUNCTION使得该函数既能够在C++中使用,又能够在蓝图中使用
具体操作如下:创建一个名为Warrior的Actor,在里面定义一个UFUNCTION
//Warrior.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Warrior.generated.h"
UCLASS()
class CHAPTER_04_API AWarrior : public AActor
{
GENERATED_BODY()
public:
AWarrior();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
UPROPERTY(EditAnywhere)
FString Name;
UFUNCTION(BlueprintCallable)
FString ToString();
};
//Warrior.cpp文件中只需要添加ToString()的实现
FString AWarrior::ToString()
{
return FString::Printf(TEXT("An instance of AWarrior:%s"),*Name);
}
Actor类可以被拖放到场景中,在Level蓝图中即可使用:
对Actor进行销毁操作:使用Destroy和Timer
先前在GameMode中定义了Actor的实例化操作,继续完善代码:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "UECookbookModeBase.generated.h"
UCLASS()
class CHAPTER_04_API AUECookbookModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
virtual void BeginPlay()override;
UPROPERTY()
class AMyFirstActor* SpawnedActor;
UFUNCTION()
void DestroyActorFunction();
};
.h文件中主要多了:
一个AMyFirstActor对象指针来存储实例化后Actor的地址,
一个UFCUNTION来销毁Actor
#include "UECookbookModeBase.h"
#include "MyFirstActor.h"
//#include "Engine.h"
void AUECookbookModeBase::BeginPlay()
{
Super::BeginPlay();
GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, TEXT("Actor Spawning"));
FTransform location;
SpawnedActor=GetWorld()->SpawnActor<AMyFirstActor>(AMyFirstActor::StaticClass(), location);
FTimerHandle Timer;
GetWorldTimerManager().SetTimer(Timer, this, &AUECookbookModeBase::DestroyActorFunction, 10);
}
void AUECookbookModeBase::DestroyActorFunction()
{
if(SpawnedActor!=nullptr)
{
GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, TEXT("Actor Destroy"));
SpawnedActor->Destroy();
}
}
.cpp文件中主要是设置了一个定时器,定时结束后调用DestroyActorFunction()
分析:Actor与Object不同,Actor的销毁实际上调用Destroy
除了将Timer和Destroy组合以达到定时销毁Actor的功能,也可以在Actor中使用SetLisfeSpan()来设置Actor的生命周期
在Warrior的BeginPlay中添加SetLifeSpan(10); 即可看到如下的效果
为MyFirstActor添加Component,并且通过FObjectFinder来指定Assets。
为了完成本节书本的样例,同时又不使用StarterContent内容(为了最小化上传Git上的内容),这里自定义了一个StaticMesh,和Material。
在MyFirstActor的头文件中添加,添加EditAnywhere的目的是可以在Editor面板中指定
UPROPERTY(EditAnywhere)
UStaticMeshComponent *Mesh;
在.cpp的构造函数中添加:
Mesh = CreateDefaultSubobject<UStaticMeshComponent>("MeshComponent");
并通过代码指定Assets
auto MeshAsset = ConstructorHelpers::FObjectFinder<UStaticMesh>(
TEXT("StaticMesh'/Game/SM_Cube.SM_Cube'"));
if(MeshAsset.Object!=nullptr){
Mesh->SetStaticMesh(MeshAsset.Object);
}
其中ConstructorHelpers会爆红,但编译通过,需要添加"ConstructorHelpers.h"
这里开始编译器的代码构建就不能够及时的更新到引擎之中,需要使用UE4Editor来编译。
除非自己实在不想使用蓝图,才会用FObjectFinder这种方式来为StaticMesh指定资源。最快捷的方式是在MyFirstActor的基础上造一个子类蓝图,在蓝图中手动指定Mesh,这样可以提高效率,并且方便后期调整资源。
创建基于GameState的MyGameState类,主要体现继承的方式来完善Actor的思想
//MyGameState.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "MyGameState.generated.h"
UCLASS()
class CHAPTER_04_API AMyGameState : public AGameStateBase
{
GENERATED_BODY()
AMyGameState();
public:
UFUNCTION()
void SetScore(int32 NewScore);
UFUNCTION()
int32 GetScore();
private:
UPROPERTY()
int32 CurrentScore;
};
//MyGameState.cpp
#include "MyGameState.h"
AMyGameState::AMyGameState()
{
CurrentScore = 0;
}
void AMyGameState::SetScore(int32 NewScore)
{
CurrentScore = NewScore;
}
int32 AMyGameState::GetScore()
{
return CurrentScore;
}
本节拓展读物:
Attaching components用以形成层级(父子关系):
创建一个名为HierarchyActor的Actor类
DEPRECATED(4.12, "This function is deprecated, please use AttachToComponent instead.")
bool AttachTo(...);
所以说书中并没有修正这个代码,正解要把AttachTo替换为 AttachToComponent或者SetupAttachment,其中SetupAttachment参数少。
//HierarchyActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "HierarchyActor.generated.h"
UCLASS()
class CHAPTER_04_API AHierarchyActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AHierarchyActor();
UPROPERTY(VisibleAnywhere)
USceneComponent* Root;
UPROPERTY(VisibleAnywhere)
USceneComponent* ChildSceneComponent;
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* BoxOne;
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* BoxTwo;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
};
//HierarchyActor.cpp
AHierarchyActor::AHierarchyActor()
{
Root = CreateDefaultSubobject<USceneComponent>("Root");
ChildSceneComponent = CreateDefaultSubobject<USceneComponent>("ChildSceneComponent");
BoxOne = CreateDefaultSubobject<UStaticMeshComponent>("BoxOne");
BoxTwo = CreateDefaultSubobject<UStaticMeshComponent>("BoxTwo");
auto MeshAsset = ConstructorHelpers::FObjectFinder<UStaticMesh>(
TEXT("StaticMesh'/Game/SM_Cube.SM_Cube'"));
if (MeshAsset.Object != nullptr) {
BoxOne->SetStaticMesh(MeshAsset.Object);
BoxTwo->SetStaticMesh(MeshAsset.Object);
}
RootComponent = Root;
BoxOne->SetupAttachment(Root);
ChildSceneComponent->SetupAttachment(Root);
BoxTwo->SetupAttachment(ChildSceneComponent);
ChildSceneComponent->SetRelativeTransform(FTransform(
FRotator(0, 20, 30), FVector(250, 0, 20), FVector(0.1f)));
}
创建一个ActorComponent,名为RandomMovementComponet,具有行为,不被渲染
ActorComponent的作用是附加到Actor上,获取父物体的信息,并设置父物体的属性。主要是通过GetOwner()来获取是当前属于哪个Actor.
创建一个SceneCompont,名为ActorSpawnerComponent,是ActorComponent的子类,不被渲染
作用:具有位置属性,可以用来作为物体的发射点。
主要是SpawnActor有多个重载形式,以及Transform的位置上传入的应该是一个地址,FTransform本身是一个结构体(但是不用&也没有出问题,从书上之前的例子上可以看出来),但是最好是取地址
出于篇幅限制,仍然是把第四章剩下的部分放到下一节