虚幻引擎(UE4)中重要的Class类

虚幻引擎4(简称UE4)的GamePlay框架提供了一套强大的类来构建游戏。你的游戏可以是一个射击游戏,农场模拟器游戏,一个深度的RPG游戏,GamePlay框架可以帮助你实现这些复杂的工作。理解这个框架对于成功和高效是至关重要的。

适用群体

对UE4感兴趣,特别是那些正在学习UE4C++,并想了解更多关于虚幻的游戏框架的开发者。这篇文章介绍了您将在游戏框架中使用的核心类,并解释了它们的用法、引擎如何实例化它们以及如何从游戏代码的其他部分访问这些类。提供的大多数信息也适用于蓝图。

目录

Actor

ActorComponet

PlayerController

AIController

Pawn

GameModeBase

HUD

World

GameInstance

PlayerState

GameStateBase

UObject

GamePlayStatics

推荐阅读


GamePlay框架的类

在虚幻引擎4中构建游戏时,你会发现很多模板已经为你准备好了。在C++中,你会用到大量的类来制作游戏。我将介绍每一个类,它们拥有的一些简洁的特性,以及如何从代码中的其他地方引用它们。本指南中的大多数信息都适用于蓝图,尽管我使用C++代码段,但某些功能可能不暴露于蓝图,因此只与C++开发者有关。

 

 

Actor

        生命周期,网络复制,碰撞事件,伤害。

这可能是你在游戏开发中最常见的类,Actor是游戏中Level中所有对象的基类,包括玩家,敌人,门,墙和其他游戏对象。Actor是使用ActorComponent(见下一节)构建的,比如StaticMeshComponent、CharacterMovementComponent、ParticleComponent等等。甚至像GameMode这样的类(见下文)也是(即使GameMode在世界上没有“真实”的位置)。下面我们讲解关于Actor的一些内容。

在多人游戏中,Actor是可以通过网络复制的类,通过在Actor类对象的构造函数中调用SetReplicates(true)实现复制。Actor可以接受各种伤害,伤害可以通过MyActor->TakeDamage(...)或者UGamePlayStatics::ApplyDamage(...)直接将伤害作用到Actor上,对于不用类型的伤害有不同的调用方法,例如武器点伤害和范围爆炸类伤害。在虚幻的官方网站上有一个关于UE4伤害的详细介绍。

你可以使用GetWorld()->SpawnActor(...)在代码中轻松的生成一个Actor对象,其中T是生成并返回的Actor实例类型,例如最基础的AActor类,AgadgetActor. AGameplayProp 等。

代码如下:

生成 FTransfrom SpawnTM;

FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandingOverride = ESpawnActorCollisionHandingMethod::AlwaySpawn;
SpawnParams.Owner = GetOwner();

/* Attempt to assign an instigator (used for damage application) */
SpawnParams.Instigator = Cast(GetOwner());
ASEquippableActor* NewItem = GetWorld()->SpawnActor(NewItemClass, SpawnTM, SpawnParams);

有很多方法访问Actor对象,通常你可以使用一个指针或者引用来访问Actor对象的成员。上面的示例代码中,可以用通过NewItemClass(ASEquipableActor类型的指针)来对NewItemClass的成员进行操作。

一个特别有用的函数UGamePlayStatics:GetAllActorsOfClass(...), 它可以让你获取传入参数类型的所有派生类型对象的数组列表,如果你传入Actor,几乎可以获取整个Level中所有的对象。这个函数也许对你来说并不重要,因为确实对游戏用处不大,但是它可以作为你调试Level的工具函数,让你更加清楚整个Level都存在哪些Actor。

Actor自己并不具备变换特性(平移、缩放、旋转)。这些变换特性是通过Actor中的RootComponent实现的,例如SceneComponents层次结构中的顶级组件(有关SceneComponents的更多信息,请参见下文)。非常费用的函数如:MyActor->GetActorLocation()获取Actor中RootComponent组件的世界空间位置信息包括(平移、缩放、旋转)。

这里有一些很有用的函数,也是Actor使用最频繁的函数。

  • BeginPlay    //当Actor生成的时候并且初始化完成之后,第一个调用的的函数。在这里你可以处理你的基础游戏逻辑,定时器,或者改变一些已经初始化完成的成员属性和查询它的运行环境。
  • Tick  //每帧调用,对于绝大多数的Actor来说,处于程序性能考虑这个函数会被关闭。但是它默认是可用有效的。非常适合每帧设置动态逻辑和按帧检测内容。最终你将会将大量的代码逻辑转移至基于事件或者定时器的低频逻辑上执行。
  • EndPlay  //当Actor在从世界中被移除时调用。
  • GetComponentByClass  //查找指定的单个组件实例,当你没有某个Actor确切,但又知道它应该包含一个特定的组件类型时非常有用。同时GetComponetsByClass 返回的是找到的所有对象实例,并不是只有第一个。
  • GetActorLocation  //获取Actor的位置信息,包括坐标,旋转和缩放。与之相反的是SetActorLocation
  • NotifyActorBeginOverlap  //灵活地检测被其他组件重叠,触发碰撞。
  • GetOverlappingActors  //快速查找当前正在与自己发生重叠(碰撞)的Actor,返回一个数组, 它还要一个组件变体 GetOverlappingComponents

Actor包含了大量的成员变量和函数,它是虚幻引擎GamePlay框架的基石,所以不必惊讶。为了更好地了解Actor类,一种的办法是在Visual Studio编辑器中打开Actor类的头文件(.h),并查看可用的功能。这将涉及到更多的知识,所以我们移动到下一个类。

推荐阅读官方文档:

  • Actor 生命周期

 

ActorComponet

组件存在于Actor的内部,常见的组件包括StaticMeshComponent(静态网格组件), CharacterMovementComponent(人物移动组件), CameraComponent(摄像机组件)和SphereComponent(球形组件)。 这些组件各自处理特定的任务,例如移动,物理交互(例如一个碰撞体积,只负责检测重叠碰撞)或者虚拟演示一些像角色网格的事物。

ActorComponet的一个常见的子类是SceneComponent, 同时ActorComponet还是所有具有变换特性(平移,缩放,旋转)和支持附件的类对象的基类。例如,你可以将你的(CameraComponent)摄像机组件设置为SpringArmComponent(摄像机臂组件,类似于自拍杆)的附件来实现第三人称摄像机,同时拥有变换特性和附件特性要求开发者妥善管理相关的位置信息。

组件绝大多数情况在Actor的构造函数中被创建,你也可以在运行时创建和销毁组件。先让我看一个Actor对象的构造函数。 

ASEquippableActor::ASEquippableActor()
{
 PrimaryActorTick.bCanEverTick = true;

 MeshComp = CreateDefaultSubobject(TEXT("MeshComp"));
 MeshComp->SetCollisionObjectType(ECC_WorldDynamic); // Just setting a property on our component
 // Set our root component for other SceneComponents to attach to 
 RootComponent = MeshComp; 

// not attached to anything (not a SceneComponent with a transform) 
ItemComp = CreateDefaultSubobject(TEXT("ItemComp")); 

}

USkeletalMeshComponent是使用Actor类中的函数CreateDefualtSubject创建的,你需要指定一个名字作为参数(在蓝图可见)。如果你使用C++开发游戏工程,在构造函数中你将大量使用这个函数。

你也许会注意到上面的代码中我们设置了MeshComp做为新的RootComponent,现在所有的SceneComponent都必须依附于这个MeshCoonent。利用下面两行代码将WidgetComp组件设置为根组件MeshComponent的附件:

WidgetComp = CreateDefaultSuboject(TEXT("interactWidgetComp"));
WidgetComp->SetupAttachment(MeshComp)

SetupAttachComponent函数用来初始化场景中所有组件的依赖关系,它将会被除了RootComponent(根组件)之外的所有场景组件调用。换句话说,UE4已经提供了一个根组件,我们只需要将整个根组件作为树根,将其他场景组件添加到根组件上,呈现一个树形结构,这些都将在构造函数里完成。你也许想知道为什么这里的ItemComponent 没有调用SetupAttachment函数, 这仅仅是因为该组件是ActorComponent类型,并不是一个SceneComponent类型的组件,它没有Transfrom变换特性(平移,旋转和缩放),因此不需要添加到场景的层级结构树上。不管怎样,该组件将失踪被注册为Actor类型的对象,所以MyActor->GetComponentByClass函数返回的是所有ActorComponent和SceneComponent对象。

这些组件和Actor是使用c++和蓝图开发游戏的主要部分,构成了游戏的基石。你可以很容易在游戏中创建你自己的自定义组件,来处理一些特定的事情,例如一个HealthComponent组件保存了角色的生命值,并且与之关联的Actor对象进行伤害交互。

在GamePlay期间,你可以使用如下代码生成你自己的组件,这与CreateDefualtSuboject只能在构造函数中生成对象不同

UActorComponent* SpawnedComponent = NewObject(this, UStaticMeshComponent::StaticClass(), TEXT("DynamicSpawnedMeshComponent"));
if (SpawnedComponent)
{
    SpawnedComponent->RegisterComponent();
}

在ActorComponent中有一些很有用的成员包括:

TickComponent() // 这和Actor类的Tick函数很相似,每帧调用,用来处理一下高频率更新的逻辑。

bool bIsActive 和其相关函数像Activate, Deactivate用来启用或者禁用ActorComponent组件,而不是将该组件从Actor对象销毁或者移除,仅仅是禁用或者启用。

为了启用ActorComponent的网络复制功能,你必须调用SetIsReplicated(true), 这和Actor中的相应的网络复制有点不同,如果你只是想复制Actor中一段特定的组件的逻辑(像复制一个变量),而不是复制Actor上的所有组件, 此时ActorComponent的SetIsReplicate方法是很必要的。

 

PlayerController

角色对象最主要的一个类,接收玩家的输入信息。PlayerController类它自己一般不会显示在游戏场景里,但是它存在于场景,换句话说它是在场景中是一个虚拟的存在,没有任何的视觉表现。在场景中他是一个虚拟的具有物理特性的角色对象。在整个游戏进程中,一个玩家Player可以拥有多个Pawns(例如一辆汽车,一个Pawn复制的副本),而PlayerController作为这些Pawns的拥有者在整个Level中保持不变,这一点很重要,也有可能某个时候PlayerController根本不拥有任何的Pawns,这就像打开的菜单里没有任何的条目,你需要向PlayerController中添加一些Pawns对象。

在多人游戏人中,PlayerController仅仅存在于拥有玩家角色的客户端和服务器上,这意味着一个5人游戏,服务器上拥有5个PlayerController对象,而客户端只有属于自己的那1个PlayerController对象。这对于理解将某些变量放在何处非常重要,因为当所有玩家都要求复制一个玩家的变量时,它不应该存在于PlayerController中,而应该存在于Pawn甚至PlayerState(稍后讨论)中。

获取PlayerController

  • GetWorld()->GetPlayerControllerIterator()  //GetWorld是任何的Actor的成员函数。
  • PlayerState->GetOwner() //PlayerState的拥有者是一个PlayerController类型,你需要手动转换为你自己的PlayerController类型
  • Pawn->GetController() //仅仅在被设置为Pawn对象的拥有者后才能正确的返回。如果没有该Pawn没有设置占有该PlayerController,则该函数返回为空。

此类包含了一个PlayerCameraManager对象,该对象用来处理视口目标和摄像机变换,包括摄像机的震动。PlayerController处理的另一个重要的类是HUD(稍后讨论),HUD被用于渲染画布(现在用得不是很多了,现在用的多的是UMG)和很好的管理一些你想传递给UMG接口的数据。

当一个新玩家加入游戏GameMode,一个新的PlayerController将通过GameModeBase中的OnLogin()函数被创建。

 

AIController

这和PlayerController的概率相似,区别是它只是控制AI而不是玩家。它就像一个大脑可以拥有并控制一个Pawn对象,这点很像PlayerController,AIController是Pawn控制者的代理对象,可以决定Pawn的行为。

行为树和任何AI能看见或者听见的事物都可以通过访问这个控制器,并将决策推送给Pawn并让其执行。

获取AIController

AIController在Pawn对象中获取和PlayerController一样通过GetController获取。在蓝图中通过任何地方都可以调用的GetAIController函数来获取, 但是传入的参数必须是被AIController控制的Pawn对象。

生成AIController

AIController对象可以通过设置Pawn对象的成员变量Auto Possess AI自动生成。当使用这样的方式生成时,确保你设置了Pawn对象中的变量"AI Controller Class"

 

Pawn

玩家(或人工智能)控制的物理和视觉表现,他是一个具有移动功能的Actor,它依然是不可见的,就像是一个游戏中的透明人,有和其他可见玩家一样的数据,只是没有视觉表现。 可以是汽车、战士、炮台等其他代表你游戏人物角色的对象。Pawn一个常用的子类Character、该类实现了网格SkeletalMesh和更重要的移动CharacterMovementComponent,并提供了很多选项用来微调玩家在游戏场景中各种属性数据。

在多人游戏中,每个Pawn实例将被复制到其他的客户端,这意味着5人游戏中,服务器和每一个客户端都拥有5个Pawn实例。 当玩家在游戏中死亡时,销毁一个Pawn对象实例,待玩家重生时,生产一个新的实例对象,这样的处理方式并不少见。记住这一点,以便在玩家的单个生命周期之外存储您想要保留的数据(或者不要完全使用此模式,而是让Pawn实例保持活着状态)

获取Pawn

  • PlayerController->GetPawn()  //当且仅当PlayerController已经被至少一个Pawn对象拥有。
  • GetWorld()->GetPawnIterator() //GetWorld是所有Actor的成员函数。返回所有Pawn的列表包括AI。

生成Pawn

GameModeBase 生成Pawn通过SpawnDefaultPawnAtTransfrom, GameModeBase类还需要指定要生成的Pawn名字。

 

GameModeBase

指定使用哪个类(PlayerController、 Pawn、 HUD、GameState、PlayerState)的主类,通常用于指定游戏规则,比如说处理其他关键属性,例如生成玩家。

GameMode是GameModeBase的基类,包含了很多被用于Unreal Tournament的(比如MatchState和其他射击类游戏)原生特性。

在多人游戏中, GameMode存在于服务器上, 这意为着所有客户端没有实例对象,这对于单人游戏没有影响。为了复制函数和储存你需要储存的GameMode数据,你需要考虑使用GameState,GameState存在于所有的客户端,它是专门为此目的而设计的类。

获取GameMode

  • GetWorld()->GetAuthGameMode()  //GetWorld是所有Actor的成员函数,随时可用。
  • GetGameState() //返回复制函数或变量的GameState。
  • InitGame(...) //初始化一些游戏规则,这包含URL上提供的规则,这些规则可以在关卡Level加载阶段可以被当做参数传入。

 

HUD

用户UI界面类,拥有很多绘制Canvas代码,在UMG出现之前——如今基本被UMG取代了

HUD存在于客户端,不适合复制,被PlayerController拥有控制

获取HUD

PlayerController->GetHUD() //可在你本地的PlayerController中使用

生成

在拥有HUD的PlayerController中通过SpawnDefaultHUD生成, 然后被GamwModeBase通过InitializeHUDForPlayer重写

题外话

现在UMG已经基本取代了HUD,UMG几乎实现了HUD所有的功能,并有了更加强大的功能。记住在多人游戏中你需要生成Widget组件,首先要确保PlayerController是IsLocalController为true。

 

World

UWorld 是最顶层的Level对象,是Actor和各种组件存在和被渲染的地方的一张地图。包含了不间断的关卡Level和许多其他对象,比如GameState,GameMode和当前在地图上的Pawns和Controllers的列表。

光线追踪和其所有变体都是通过World中的函数(比如World->LineTraceSingleByChannel)实现的。

获取World

在任何一个Actor中通过GetWorld很容易获取到

当在静态方法中获取World你需要传入一个参数"WorldContextObject"。然后我们用这个参数来调用GetWorld(),请看下面的例子:

静态方法
static APlayerController* GetFirstLocalPlayerController(UObject* WorldContextObject);


使用:
    UObject* Context;
    GetFirstLocalPlayerController(Context)
    Context->GetWolrd();

GameInstance

在整个GamwPlay进程中,至始至终,只有一个GameInstance实例,从地图到菜单始终都是维护此对象实例。该类可以用于提供网络消息,加载游戏数据和一些与指定关卡lLevel无关的事件处理。

获取GameInstance

  • GetWorld()->GetGameInstance():   // T是需要返回并转换的类型,例如GetGameInstance()。<>里可以是你从GameInstance派生的类型
  • Actor->GetGanemInstance()

题外话

通常在游戏开始早期不宜使用太多,除非你对UE(游戏会话,demo回放或者保存一个关卡之间的数据)理解很深刻。

 

PlayerState

此类包含了一些变量,这些变量可以在客户端和服务器之间进行复制, 数据并不会运行它自己的逻辑。当客户端上的玩家死亡后,Pawn通常就会被销毁,PlayerState就是储存玩家死亡之后的的游戏数据的好地方。

获取PlayerState

Pawn拥有自己的成员函数Pawn->PlayerState,同时你也可以用Controller->PlayerState。在Pawn里,只有设置了possess占有的Controller。PlayerState才会被赋值,否则它将是一个NULL

获取PlayerSate上的所有的玩家,可以使用GameState->PlayerArray

生成

PlayerState在GameMode中创建并赋值,通过AController::InitPlayerState()

题外话

  • 只有在多人游戏中才有用
  • 要使用派生的PlayerState,你需要在GameMode的构造函数中使用(PlayerStateClass = AUTPlayer::StaticClass())

 

GameStateBase

类似于PlayerState,能为客户端提供GameMode信息。 由于在客户端没有GameMode实例,所以GameStateBase成为了一个有用的容器,用来复制像匹配时间和团队积分等的内容。

拥有两种变体,GameState和GameStateBase,其中GameState通过GameMode处理额外的变量(这与GameModeBase相反)

获取GameStateBase

  • World->GetGameState()  T就是返回的类型,可以是你从GameState派生的类型。
  • MyGameMode->GetGameState()  这种方法仅在拥有GameMode实例的服务器上才有,客户端的调用需要用上面这种调用方法

题外话

优先使用GameStateBase,除非你的GameMode是从GameMode派生而来,而不是GameModeBase。换句话说,如果MyGameMode从GameMode继承而来,则使用GameState,从GameModeBase派生而来,就使用GameStateBase。

 

UObject

引擎中所有类的基类。Actor和其他核心类(比如GameInstance)都是派生自UObject。此类不会被渲染,但它非常有用,当在C++中声明的成员的类型不太适合你的要求的时候,用UObject就能解决问题,因为是它是所有类的基类,兼容所有类型。

生成UObject

UObject的生成和Actor不同,它使用NewObject()方法来生成。举例:

TSubclassOf ClassToCreate;
UObject* NewDesc = NewObject(this, ClassToCreate);

题外话

一般情况,不要直接从UObject类派生出新的类实现一些自定义的系统,除非你对UE引擎了解够深。例如使用它来储存一个基础数据列表。

可以被用于网络,但是需要在Object类中设置一些额外的参数,并Object对象需要被储存于Actor内部。

 

GamePlayStatics

静态类。用来管理游戏中公共的功能模块,例如播放音效、生成粒子特效、生成Actor、施加伤害到Actor、获取Pawn、获取Controller等等。总之,此类对各种游戏对象的访问非常有用,因为它的所有函数都是静态函数,不需要你用指向游戏实例对象的指针来调用,任何地方,直接调用,往下看。

获取GamePlayStatics

如果GamePlayStatics 是一个UBlueprintFuntionLibrary,你可以在代码(或蓝图)中的任何地方获取它:

//静态方法,在任意地方可以调用,但一定要添加包含头文件"Kismet/GameplayStatics.h"
UGameplayStatics::WhateverFunction(); 

题外话

GamePlayStatics包含了很多有用的接口函数,做任何游戏都必须了解的类,强烈推荐 官方教程,也许你能找到有用的东西。

 

 

推荐阅读

  • Gameplay 框架文档
  • Gameplay 项目文档
  • Actor 声明周期

 

 

你可能感兴趣的:(UE4,C++和蓝图,UE4,c++,UE4,入门,VR)