UE4_20 Scripting with C++ 02

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;
}

本节拓展读物:

http://cedric-neukirchen.net/Downloads/Compendium/UE4_Network_Compendium_by_Cedric_eXi_Neukirchen_BW.pdf

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本身是一个结构体(但是不用&也没有出问题,从书上之前的例子上可以看出来),但是最好是取地址

出于篇幅限制,仍然是把第四章剩下的部分放到下一节

 

猜你喜欢

转载自blog.csdn.net/weixin_33232568/article/details/89190346