大家好,我是阿赵。
之前在介绍HelloWorld的时候,我们很创建了一个MyGameModeBase的c++类,然后就可以在BeginPlay函数里面写打印的HelloWorld。这一篇主要是说一下,GameMode究竟是一个什么东西,然后UE里面的生命周期是怎样的。
一、 GameMode和关卡蓝图
1、C++程序的Main函数
如果是纯粹的C++项目,按道理都是有一个Main函数作为整个项目的启动入口。UE的C++项目实际上也是有的。不过那个是引擎本身启动的时候调用的,具体的位置在Engine\Source\Runtime\Launch\Private\Launch.cpp里面的GuardedMain函数
在这个主入口的函数里面,引擎会做很多事情,比如初始化引擎功能之类。
所以,写UE的C++不能以Main函数作为我们逻辑的入口。那应该怎么办呢?
2、关卡蓝图
用过Unity引擎的朋友,可以对比一下Unity引擎的启动方式。在Unity引擎里面的做法,一般是指定一个场景,然后在上面建一个空节点,然后挂一个继承了MonoBehaviour的脚本,通过MonoBehaviour脚本的生命周期自动运行的函数,来进入游戏实际的逻辑。
和Unity引擎一样,UE引擎在刚开始运行的时候,也是需要指定一个默认的关卡(Level)的。当这个Level被载入并运行的时候,就可以运行这个关卡对应的代码。如果从蓝图的角度看,UE有一个叫做“关卡蓝图”的东西:
打开了之后,会看见里面默认有Event BeginPlay和EventTick两个节点,然后我们就可以在里面编写逻辑。
UE的每一个Level,都会有一个对应的关卡蓝图。
3、 GameMode
GameMode的概念和关卡蓝图有点类似,它也是在游戏启动的时候会自动运行的。不过有个区别是,GameMode可以设置一个默认的,如果关卡本身不指定单独的GameMode,那么每个关卡在载入的时候,都会运行默认的同一个GameMode代码,然后每一个Level也可以单独指定属于自己的一个特殊的GameMode。
这样的操作,就有点类似于在Unity引擎的关卡里面挂一个空物体再挂一个MonoBehaviour的情况,反正这个关卡载入成功后,就会自动调用这个指定的GameMode里面的代码,并且运行生命周期。
所以,关卡蓝图和GameMode是可以同时存在的,但两者做的事情有点类似。
二、 Actor和GameModeBase
1、 Actor
如果我们创建一个蓝图类,会看到可以让我们选择父级:
然后最基础的父类,就是Actor,这个东西,对比Unity引擎来说,其实就是类似于GameObject,它是一个可以放置在场景里面的对象,包含着一些基础的Transform属性,还有生命周期。
双击打开Actor的蓝图,会看到里面有EVent BeginPlay、Event ActorBeginOverlap和Event Tick三个默认的节点。这和之前的关卡蓝图有点类似,其实都是代表着这个Actor的生命周期,比如BeginPlay就是在Actor被载入的时候会运行一次。
实际上创建其他蓝图类型,他们的基类都是Actor。
2、 GameModeBase
再来创建一个游戏模式基础的蓝图看看:
这里我把这个游戏模式的蓝图命名为”GameModeBaseBP”:
可以发现,在之前指定游戏模式的地方,会同时出现之前用C++写的MyGameModeBase,和用蓝图创建的GameModeBaseBP。
所以,这个GameModeBase,既可以用C++实现,又可以用蓝图实现。
双击打开这个GameModeBaseBP蓝图,会看到和Actor是差不多的,也是有BeginPlay和Tick的生命周期。
当然,Actor的生命周期不止这两个,还有其他,比如可以添加一个EndPlay的生命周期。
又来对比一下Unity引擎,Unity引擎里面的生命周期,都是由MonoBehaviour而来的,所以继承MonoBehaviour的所有类都可以使用Awake、Start、Update、OnDestroy之类的生命周期。
那么UE这边,因为都是基础Actor的,所以Actor的生命周期,比如BeginPlay、Tick和EndPlay之类的,其他继承Actor的类都能使用,包括GameModeBase。
三、 生命周期说明
如果用C++来编写GameModeBase,那么需要这样声明生命周期:
MyGameModeBase.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MyGameModeBase.generated.h"
/**
*
*/
UCLASS()
class UECPPTEST_API AMyGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
AMyGameModeBase();
public:
virtual void BeginPlay();
virtual void Tick(float DeltaTime);
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason);
};
MyGameModeBase.cpp
#include "MyGameModeBase.h"
AMyGameModeBase::AMyGameModeBase()
{
PrimaryActorTick.bCanEverTick = true;
}
void AMyGameModeBase::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Display, TEXT("On level Start"));
}
void AMyGameModeBase::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
UE_LOG(LogTemp, Display,TEXT("OnUpdate:%f"),DeltaTime);
}
void AMyGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
FString str = StaticEnum<EEndPlayReason::Type>()->GetNameStringByValue((int64)EndPlayReason);
UE_LOG(LogTemp, Display, TEXT("End Play:%s"), *str);
}
说明:
BeginPlay
相当于Unity的Start,在Actor对象刚开始播放时调用
Tick
相当于Unity的Update,但默认是不会调用的,需要在构造函数的时候开启:
PrimaryActorTick.bCanEverTick = true;
EndPlay
相当于Unity的OnDestroy,在Actor对象被销毁的时候调用,其中EndPlayReason是销毁的原因。
对应的枚举EEndPlayReason::Type
enum Type : int
{
/** When the Actor or Component is explicitly destroyed. */
Destroyed,
/** When the world is being unloaded for a level transition. */
LevelTransition,
/** When the world is being unloaded because PIE is ending. */
EndPlayInEditor,
/** When the level it is a member of is streamed out. */
RemovedFromWorld,
/** When the application is being exited. */
Quit,
};
如果要打印这个枚举,可以:
FString str = StaticEnum<EEndPlayReason::Type>()->GetNameStringByValue((int64)EndPlayReason);
UE_LOG(LogTemp, Display, TEXT("End Play:%s"), *str);