Unreal Engine 4 Scripting with C++ Cookbook 出了第二版。
新书提供的改进:采用4.21编写全书代码与将旧书的80条策略提升到了100条。全书14章节,其中多出了multiplay network章节(第十四章),另外将integrate c++with UE4 Editor分成了两个章节(第九、十章)。该书对应Code可以从github上获取 https://github.com/PacktPublishing/Unreal-Engine-4.x-Scripting-with-C-Cookbook---Second-edition.。我的学习测试代码使用4.20版本。
单词本:
in conjunction with:与
It will help you understand the benefits of using C++ in conjunction with the many tools included with this powerful game engine.
object-oriented:面向对象
This book assumes familiarity with the C++ programming language,an understanding of what different programming operators mean, and basic knowledge of the concepts of object-oriented programming
elaborate:精心制作的
Creating a game is an elaborate task that will require a combination of assets and code
asterisk:星号
There will be an asterisk * just before the FString variable when using UE_LOG to dereference the FString to a regular C-style TCHAR pointer.
bloated:臃肿的
your console may become bloated with messages and make it difficult to find things you are looking for.
Chapter01:UE4 Development Tools,总结如下(与第一版本内容基本相同)
讲了怎么装软件与基础操作
VS2017:
编译与运行: ctrl + shift +B build the project,ctrl +f5 run the project
在文件之间导航:Ctrl + - 会后退
Ctrl + shift + - 会前进
番茄插件:alt + o用于在.h和.cpp文件间切换
alt + g 用于查找函数原型
代码段展开与关闭 : ctrl +M
鼠标操作:
用于选取单词 crtl +点击
box选取单词 alt +left click +drag
信息打印手段:UE_LOG
UE_LOG(LogTemp,Warning,TEXT("Some Warning Message"));
像printf一样的使用:注意点FString类型的需要加*解引用
int intVar=5;
float floatVar=3.7f;
FString fstringVar="an fstring variable";
UE_LOG(LogTemp,Warning,TEXT("TEXT,%d %f %s"),intVar,floatVar,*fstringVar);
构造出一个FString:通过FString::Printf()或者FString::Format()
FString name = "Tim";
int32 mana = 450;
FString string = FString::Printf( TEXT( "Name = %s Mana =
%d" ), *name, mana );
//////////////////////////////////////////////////////////////
FString name = "Tim";
int32 mana = 450;
TArray< FStringFormatArg > args;
args.Add( FStringFormatArg( name ) );
args.Add( FStringFormatArg( mana ) );
FString string = FString::Format( TEXT( "Name = {0} Mana = {1}" ), args );
UE_LOG( LogTemp, Warning, TEXT( "Your string: %s" ), *string );
Chapter02:Creating Classes
创建一个基于Uobject的类UserProfile,
当使用UCLASS时,用户的类的构建和销毁交给了UE4来处理:
使用ConstructObject()来创建,使用UObject::ConditionalBeginDestroy()来销毁
继承自UObject分支的类以U开头,继承自AActor的类以A开头
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "UserProfile.generated.h"
UCLASS()
class CHAPTER_02_API UUserProfile : public UObject
{
GENERATED_BODY()
};
UE4会基于UCLASS创建一堆的宏,放在XXX.generated.h中,方便用户的操作
注意,UE4.15版本起,要求用户手动添加一些必要的头文件,只有用到的头文件才被加载进去,这样做的好处在于加快编译速度。
用户想要添加头文件需要放在generated.h之前
UCLASS的行为
UCLASS中传入关键字用于定义和蓝图的交互:
Blueprintable:表示该类可以被蓝图类继承
BlueprintType:表示该类作为其他蓝图的一个variable变量类型
基于自定义的UCLASS来创建蓝图
创建蓝图子类的好处在于可以通过Editor来可视化的修改具有UPROPERTY属性的值,
另外可以避免把资源hardcoding硬编码到C++中,更符合面向对象的思想。
There is a way to load resources (such as textures) using FStringAssetReferences and StaticLoadObject. These pathways to loading resources (by hardcoding path strings into your C++ code) are generally discouraged, however. Providing an editable value in a UPROPERTY() and loading from a proper concretely typed asset reference is a much better practice.
上文主要意思是说,也可以在C++中通过FStringAssetReferences来引用Texture资源或
使用StaticLoadObject来引用物体资源,比硬编码的方式要合适,
当然最合适的是使用UPROPERTY暴露给子类蓝图,然子类蓝图来修改
创建一个用户可编辑的UPROPERTY
每个UCLASS类中都可以设置任意数量的UPROPERTY
通过传入关键字定义UPROPERTY的属性:
EditAnywhere,表明可以在Code中修改,或者在蓝图Editor中修改
BlueprintReadWrite,表明子类蓝图可以在任何时间修改该属性。
EditDefaultOnly:表明子类蓝图本身可以修改,在运行时不能够修改
EditInstanceOnly:只能在实例化的对象身上修改,子类蓝图本身不能修改
BlueprintReadOnly:需要C++来设置,蓝图子类上不能修改
修改代码如下:
UCLASS(Blueprintable)
class CHAPTER_02_API UUserProfile : public UObject
{
GENERATED_BODY()
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Armor;
UPROPERTY(EditAnywhere,BlueprintReadWrite)
float HpMax;
};
使用蓝图子类,就可以修改属性值
实例化创建的UObject类:
在LevelBlueprint中:(在第一版中只介绍了使用代码ConstructObject<>来实例化,感觉并不如蓝图直观)
使用C++来实例化Uobject,这里的思想在于实例化蓝图类,由编译时确定转到运行时确定,使用未来,是一种面向对象的思想
we simply have to provide a user-editable UPROPERTY with aTSubclassOf<C++ClassName> typed variable. Alternatively, you can use FStringClassReference to achieve the same objective.
想要实例化蓝图类对象,需要知道:蓝图类的名称和蓝图类所继承的基类类型
这里说明C++自己不应该知道蓝图类的具体类型,我们可以用TSubclassOf<C++ClassName>或者是 FStringClassReference 来存储类类型,通过UPROPERTY暴露给Editor,在Editor中指定。
通过GameMode类实例化UserProfile蓝图类:.h文件,主要是使用TSubclassOf<C++ClassName>来存储类名称
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "UserProfile.h"
#include "Chapter_02GameModeBase.generated.h"
UCLASS()
class CHAPTER_02_API AChapter_02GameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
TSubclassOf<UUserProfile> BPClassName;
};
.cpp文件,使用NewObject体现了工厂模式(设计模式这一块我也是刚开始看,还不明白),NewObject需要传GetTransientPackage(),原书中说使用NewObject比ConstructObject更好,更符合设计模式。另外销毁UObject使用了ConditionalBeginDestroy()方法。
void AChapter_02GameModeBase::BeginPlay(){
AChapter_02GameModeBase *gm = Cast<AChapter_02GameModeBase>(
GetWorld()->GetAuthGameMode());
if (gm)
{
UUserProfile* newObject=NewObject<UUserProfile>((UObject*)GetTransientPackage(),
UUserProfile::StaticClass());
if(newObject){
newObject->ConditionalBeginDestroy();
newObject = nullptr;
}
}
}
这里还提到了\Program Files (x86)\Epic Games\Launcher\Engine\Config\BaseEngine.ini记录了GC的时间,并且可以通过GetWorld()->ForceGarbageCollection(true) 强制进行垃圾回收
gc.TimeBetweenPurgingPendingKillObjects=61.1
创建一个结构体;果然印证了我之前的想法,这部分作者是手动用VS创建的代码。。。
。。。
注意几点吧,文件名为ColoredTexture.h
但是具体书写的时候
#include "ObjectMacros.h"
#include "ColoredTexture.generated.h"
USTRUCT(Blueprintable)
struct CHAPTER_02_API FColoredTexture //加上了F,类似于Object的U和Actor的A
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = HUD)
UTexture* Texture;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = HUD)
FLinearColor Color;
};
GENERATED_USTRUCT_BODY()会生成ColoredTexture.generated.h
UTexture* Texture虽然爆红,但是编译通过,另外之前的TSubclassOf<UObject>会爆红,也能通过编译
最后在UserProfile文件中添加#include "ColoredTexture.h"和
UPROPERTY(EditAnywhere)
FColoredTexture Texture;
创建一个UENUM
直接写在了UserProfile中
UENUM()
enum Status {
Stopped UMETA(DisplayName = "Stopped"),
Moving UMETA(DisplayName = "Moving"),
Attacking UMETA(DisplayName = "Attacking"),
};
使用的注意事项:使用TEnumAsByte容器来存储变量
UPROPERTY(EditAnywhere)
TEnumAsByte<Status>status;
第二版把创建UFUNCTION放到了后面的章节中。
总结可以看出新版的书在细节的说明上更加的清晰。也更注重面向对象的思想。
为了单篇不过长,将后面的内容放到下一节