查看: 98|回复: 0

Unreal Engine Gameplay Framework Primer for C++

[复制链接]

4

主题

2

听众

33

积分

问题学生

Rank: 1

升级   16.5%

发表于 2021-1-13 18:13 |显示全部楼层
原文地址:Unreal Engine Gameplay Framework Primer for C++ - Tom Looman


Unreal Engine4的Gameplay Framework为构建我们的游戏提供了大量有用的class。无论你们的游戏是shooter,还是farm simulator,或者是深度RPG。Gameplay Framework都非常灵活,提供强大的游戏框架,能极大降低你们的开发负担。另外,Gameplay Framework与engine结合非常紧密,所以建议你们最好是利用好这个框架提供给你的能力,而不是跟Unity3D一样自己重新开发一套Game Framework。因此,理解这个游戏框架就是显得非常重要而有意义。


Who is this for

任何使用UE4,特别使用UE4 C++,来开发游戏的人都应该了解Unreal's Gameplay Framework。这篇文章主要解释了Gameplay Framework中核心类及其它们的用法。如何从engine中实例化这些类?如何从游戏代码的其它部分获取到这些类对象?当然,这篇文章介绍的内容在Blueprint中也是跑得通的。


Gameplay Framework Classes

当使用UE4来制作游戏时,你们会发现大量的案例模版。这些案例中包含了许多你们经常使用到的类。我们后面会详细介绍这些类,这些类的特性,及其在代码中如何引用到它们。这篇文章中大部分内容在Blueprint中也是跑得通的,而有一部分内容却只能在C++中使用。




Actor

Actor或许是你们游戏里面最常使用的类。Actor是你们场景中的player,AI enemies, doors, walls或者gameplay对象的基类。Actor由ActorComponents,像StaticMeshComponent、CharacterMovementComponent、ParticleComponent之类的组件构成。甚至像GameMode之类的类其实也是Actor,即使它在world中连个真实的position都没有。
Actor是网络上能够复制的类,你们可以通过在构造函数里面调用SetReplicates(true)即可。当我们谈到Actor中的网络部分,会涉及到大量的内容,我们在这暂时不展开介绍。
Actor支持收到damage的概念。Damage通过使用MyActor->TakeDamage()或者UGameplayStatics::ApplyDamange()直接应用在Actor上面。另外,Damage也有不同的类型,例如PointDamage(hitscan weapon)或者Radial Damage(炸弹)。
你们可以通使用GetWorld()->SpawnActor<T>()来生成一个新的Actor实例。
下面是在Runtime下生成一个Actor的参考代码:
  1. FTransform SpawnTM;
  2. FActorSpawnParamters SpawnParams;
  3. SpwanParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
  4. SpawnParams.Owner = GetOwner();
  5. SpawnParams.Instigator = cast<APawn>(GetOwner());
  6. ASEquippableActor* NewItem = GetWorld()->SpawnActor<ASEuippableActor>(NewItemClass, SpawnTM, SpawnParams);
复制代码
有许多种方法能够获取到Actor,通常我们使用pointer/referene来指向我们感兴趣的Actor。在上面的例子中,我们就可以通过NewItem来保存Actor的指针,方便后续操作该对象。
一个比较有用的函数就是UGameplayStatics::GetAllActorsOfClass(),它能将你们传入class类型的Actor全部拿到,包括派生类的Actor。但是,这个函数要尽量避免使用,因为它的效率不高。
Actor其实没有translation,rotation或者scale属性。这些属性得通过RootComponent来获取或者设置。RootComponent一般是SceneComponent类型的,它位于Actor下面Component最顶层,它有translation/rotation/scale属性。我们一般调用MyActor->GetActorLocation()本质上使用的RootComponent的Location信息。
下面是Actor比较重要的函数:
(1)BeginPlay。这个函数在Actor一旦被创建的时候调用,可以用来做些初始化的工作。
(2)Tick。每帧都被调用,对于绝大部Actor,这个函数要被关闭的,因为它会导致严重的性能问题。这个函数用于快速创建动态的逻辑,及其每帧检查某些状态。一般来说,我们尽量不用这个函数,把本来放在这个函数里面的逻辑,移到event中去或者timer中去做,来提升性能。
(3)EndPlay。当Actor从World中被删除,该函数被调用。这个函数还有一个EEndPlayReason参数,用于解释EndPlayer为什么会被调用。
(4)GetComponentByClass。这个函数用于寻找指定Actor中的component实例。这个函数在当你不知道Actor的具体类型,而知道Actor肯定有某个特定的Component类型的时候,特别有用。当然,还有一个函数GetComponentsByClass,可以寻找到Actor所有该类型的Component,而不仅仅是第一个。
(5)GetActorLocation。这系列的函数还包括*Rotation,*Scale等。
(6)NotifyActorBeginOverlap。由Actor的Components产生的Overlap事件。
(7)GetOverlappingActors。获取正与当前Actor Overlap的其它Actor,类似的接口还有GetOverlappingComponents。


Actors包含大量函数和变量,它们是Unreal Engine中gameplay framework的基石,因此不要对此感到惊讶。进一步学习该类的方法,就是打开Actor.h文件。


ActorComponent

Components存活于Actor内,常见的Components包括StaticMeshComponent,CharacterMovementComponent,CameraComponent和SphereComponent。这些Components每个完成特定的任务,例如movement,physics interaction(例如,碰撞体用于坚持两个Actor是否相交),以及在world中的视觉显示,例如player mesh。
一个ActorComponent的派生类是SceneComponent,它是所有Transform的基类,支持Attachment。举例来说,你们可以将你们的CameraComponent attach到SpringArmComponent来建立一个第三人称的camera。
Components一般在Actor的构造函数中创建。当然你可以在runtime时,创建和销毁Components。下面,我们来看一下我们的Actor构造函数。
  1. ASEquippableActor::ASEquippableActor()
  2. {
  3.         PrimaryActorTick.bCanEverTick = true;
  4.         MeshComp = CreateDefaultSubObject<UStaticMeshComponent>(TEXT("MeshComp"));
  5.         MeshComp->SetCollisionObjectType(ECC_WorldDynamic);
  6.         RootComponent = MeshComp;
  7.         ItemComp = CreateDefaultSubobject<UItemComponent>(TEXT("ItemComp"));
  8. }
复制代码
USkeletalMeshComponent使用CreateDefaultSubobject<T>来创建。如果你们使用C++来搭建你们的游戏,你们会经常使用到这个函数。
你们也许已经注意到我们创建MeshComp,并赋值给RootComponent。接下来,任何Scene Component都能够Attach到该Mesh上面。
  1. WidgetComp = CreateDefaultSubobejct<UWidgetComponent>(TEXT("InteractWidgetComp"));
  2. WidgetComp->SetupAttachment(MeshComp);
复制代码
SetupAttachement会为我们来处理Attach的事情,它能够被在构造函数中被所有的scene component调用,除了RootComponent本身。你们也许非常好奇,为什么ItemComponent不调用这个SetupAttachment函数?这是因为这个Component是ActorComponent,不是一个SceneComponent,它没有Transform(location/rotation/scale)。但是这个Component仍然被Actor注册,使用GetComponentByClass仍然能获取该ActorComponent。
Actor一起,这些Component在构建你们的游戏中是非常重要的。它们才是你们游戏真正的基座。你们也可以非常轻松地创建你们自己自定义的component,让它们来处理一些游戏中特定的事情,例如HealthComponent用于保存hitpoints,以及对于Actor收到damage的响应。
在gameplay中,你们可以使用下面的代码来创建你们自己的Components。当然,它与在构造函数中使用CreateDefaultSubobject是不同的。
  1. UActorComponent* SpawnedComponent = NewObject<UActorComponent>(this, UStaticMeshComponent::StaticClass(), TEXT("DynamicSpawned"));
  2. if (SpawnedComponent)
  3. {
  4.         SpawnedComponent->RegisterComponent();
  5. }
复制代码
ActorComponent内置的一些函数包括:
(1)TickComponent()。跟Actor's Tick函数一样,每帧都会调用,用于处理high frequency logic。
(2)bool bIsActive及其Activate/Deactive等函数。这些函数用于在不销毁component或者从Actor中移除Compnent的情况下,开启或者关闭Component
如果你们想启动Actor的replication功能,你们必须调用SetIsReplicated(true),这跟Actor类似的函数名字是完全不同的。


PlayerController

Player相关最重要的类,用于接受用户的input。PlayerController在环境中并没有视觉上的呈现,相反它拥有一个在世界中定义player的视觉和物理呈现的Pawn实例。在gameplay中,player可能有多个不同的pawn(例如,vehicle或者Pawn重新时产生的一个新的copy)。这点非常重要,因为它意味着PlayerController在某些时候可能不拥有任何的pawn。这也意味着像打开菜单这种操作应该放在PlayerController中,而不是Pawn中。
在multiplayer游戏中,PlayerController仅仅存在它自己的client和server中。这也就意味着在有4个player的游戏中,server端有4个player controller,而每个客户端只有1个。这点对于理解,当所有的player需要一个player的变量被复制,哪里放置这些变量是合适的。这些变量应该放置在Pawn或者PlayerState中,而不是PlayerController中。


Accessing PlayerController

(1)GetWorld()->GetPlayerControllerInterator()。在任何Actor实例中,GetWorld是可用的。
(2)PlayerState->GetOwner()。获取到拥有该playerstate的PlayerController,当然你们需要使用cast转换成你们自己的PlayerController。
(3)Pawn->GetController()。当pawn被某个PlayerController拥有时,能够调用这个函数。
这个类包含一个PlayerCameraManager指针,用于处理view target和camera transform。PlayerController另外一个重要的类是HUD,用于Canvas rendering,管理在你们的UMG界面上的一些数据。
无论一个新的player什么时候加入GameMode,在GameModeBase中,都会通过Login()来为这个player创建一个新的PlayerController。


AIController

AIController在概念上与PlayerController几乎是一样的,但是它是专门为AI agent服务的。它像PlayerController拥有一个Pawn,是Agent的大脑。Pawn是agent的视觉表达,而AIController能控制这些Pawn在哪出现。
Behavior Tree通过AIController来运行的。AIController能够处理任何感知数据(AI能够听到或者看到的内容),然后把它们的决定传给Pawn来执行。


Accessing AIController

从Pawn中获取AIController的方式跟PlayerController是一样的,通过GetController。在Blueprint中,有个GetAIController函数也可以获取到AIController。


Spawning

如果在Pawn中设置“Auto Posses AI”变量,AIController能够被自动spawned的。确保在Pawn中设置"AI Controller Class"为你们想要的class。


Pawn

Pawn就是player或者AI控制的物理和视觉的表达。它可以是vehicle,warrior,turrent或者其它能代码你们游戏的角色。一个常见的Pawn派生类是实现了SkeletalMesh和CharacterMovement,以及CharacterMovementComponent。
在多人游戏中,每个Pawn实例都复制和同步到其它所有的客户端。也就是说,如果游戏里面有4个player,服务端和每个客户端都将有4个Pawn实例。当player死掉或者player重新spawn一个新的实例的时候,直接杀死Pawn实例是非常常见的。记住这点非常重要,尤其是你们想在player生命周期之外来保存你们的数据。
Accessing Pawns

(1)PlayerController->GetPawn() //当PlayerController当时有一个pawn对象
(2)GetWorld()->GetPawnIterator()//在任何Actor instance中,GetWorld都是有效的,它将返回所有pawns包括AI pawn。


Spawned by

GameModeBase通过SpawnDefaultPawnAtTransform生成Pawn,GameModeBase类也能指定哪个Pawn类被Spawn。


GameModeBase

这个类是用来指定哪个类被使用(PlayerController,Pawn,HUD,GameState,PlayerState),通常用来指定游戏规则,例如'Caputre the Flag'中哪里可以处理Flags,或者在wave based shootter中处理"wave spawns"。另外,它还负责一些其它重要的特征,例如spawn player。
GameMode是GameModeBase的派生类,包含更多的特性(例如被Unreal Tournament使用的MatchState和其他的shooter type feature)。
在多人游戏中,GameMode仅存在于Server上。也就是说,没有一个client上有它的实例。对于单人游戏,这点无关紧要。为了复制函数和保存你们GameMode需要的数据,你们应该考虑使用GameState,因为它在所有的client都存在,而且专门用于干这个事情的。


Accessing GameMode

(1)GetWorld()->GetAuthGameMode();
(2)GetGameState() //返回replicating函数和变量的gamestate
(3)InitGame() //初始化Game rules,包括加载level时可以传入的URL参数,"MyMap?MaxPlayersPerTeam=2";


HUD

用户界面类,有大量用户界面绘制代码的canvas。主要是在UMG出现之前经常被使用,现在已经很少使用了。
它仅仅存在于client,不适用于replication,被PlayerController所拥有。
Accessing HUD

PlayerController->GetHUD() //在你们的local PlayerController可用


Spawned by

在PlayerController中,通过SpawnDefaultHUD来生成PlayerController,接着可以通过InitializeHUDForPlayer来重新它。


Personal Remarks

我在UE4之前,很少使用该类,UMG可以通过PlayerController来处理。不过需要注意的是,在多人游戏中,你们需要确保在spawn任何widget之前,player controller是local的。


World

UWorld是顶层的对象,它代表Actor和Componenent存在和被渲染的Map。它包含persistent level,以及其他的对象,例如gamestate, gamemode和pawn/controller列表。
Line tracing主要是通过world的函数来完成的,例如World->LineTraceSingleByChannel


Accessing World

在你们的Actor中,调用GetWorld()函数来获取World。
当你们在static函数中获取world实例的时候,你们需要传递一个WorldContextobject参数进去,然后调用->GetWorld()函数。下面是我们使用的一个例子:
static APlayerController* GetFirstLocalPlayerController(UObject* WorldContextObject);


GameInstance

GameInstance有一个实例可以完整经历游戏的整个生命期。它在map和menu之间旅行,依然可以维持这个类的同一个实例。这个类可以用来提供处理网络错误的event hook,加载像游戏设置的用户数据,以及与游戏某个level无关的特征。


Accessing GameInstance

(1)GetWorld()->GetGameInstance<T>(); //T是C++类模版
GetGameInstance<UGameInstance>()
(2)Actor->GetGameInstance()


PlayerState

它是一个容器,用于保存某个player在client/server之间复制的变量。对于多人游戏来说,它不应该是来运行逻辑代码的,而应该仅仅是数据容器,因为PlayerController并不是在所有client都有效,而且当player死掉,Pawn也经常被摧毁,因此,对于那些死亡后的数据是不合适的。


Accessing PlayerState

Pawn有个变量PlayerState,通过Controller->PlayerState也可以拿到。在Pawn中的PlayState,只有一个Controller拥有这个Pawn时,它才不为空。
通过GameState->PlayerArray也可以拿到当前所有PlayerState实例的列表。


Spawned by

在GameMode中,先赋值PlayerStateClass,然后通过AController::InitPlayerState()来spawned。


Personal Remarks

PlayerState只有在多人游戏中才比较有用。


GameStateBase

跟PlayerState比较像,但是它是为client提供GameMode信息。因为GameMode实例在client上不存在,而它在server上却是一个非常有用的容器,用来复制同步match time elapsed或者team score之类的信息。
这里有两个变量,GameState和GameStateBase。其中GameState用来处理GameMode(与GameModeBase相对应)需要的额外的变量。


Accessing GameStateBase

(1)World->GetGameState<T>()
(2)MyGameMode->GetGameState() //在你们的gamemode instance里面可用(也就是拥有GameMode的唯一实例的server), client应该使用上面那个函数。


UObject

这个类是engine中几乎所有类的基类,其中Actor就派生于UObject,以及像GameInstance一样的核心类都是派生于它。它虽然不能用于渲染什么东西,但是当struct不能满足你们存储数据的需要的时候,它就是非常有用了。


Spawning UObjects

UObjects跟Actor的spawned不一样,它使用NewObject<T>()来创建:
  1. TSubclassOf<UObject> ClassToCreate;
  2. UObject* NewDesc = NewObject<UObject>(this, ClassToCreate);
复制代码
GameplayStatics

它是一个静态类,用于处理非常常见的游戏相关的函数,例如播放声音,生成particle effect,spawn actor,应用damage到actor,获取playerpawn/playercontroller等等。一般来说,这个类对于获取各种gameplay对象是非常有用的。所有函数都是静态,也就是你们不需要使用一个pointer来指向这个class实例,你们可以在任何地方直接调用它的这些函数。


Accessing GameplayStatics

因为GameplayStatics是一个UBlueprintFunctionLibrary函数,因此你们可以在C++或者Blueprint中轻易地调用它的函数。
UGameplayStatics::WhateverFunction()


References

更多关于gameplay framework材料,请见参考。
(1)Gameplay Framework Documentation
(2)Gameplay Programming Document
(3)Actor Lifecycle Documentation

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

温馨提示:求助请到“Unity技术讨论”版块中发帖,便于集中解决!
您需要登录后才可以回帖 登录 | 立即注册

Unity3D开发中国社区 -Unity3D,Unreal ( 粤ICP备20003399号 )

GMT+8, 2021-1-16 04:54 , Processed in 0.105096 second(s), 35 queries .