首先来看一下整体框架:
红色部分为主体,从右往左为组合关系,至上而下为派生关系。
UEngine类是UE的基础,UEngine提供一些最底层的交互—与操作系统的交互,而根据不同的运行模式UE与操作系统的交互模式又有少许不同,所以UEngine又派生出了UGameEngine和UEditerEngine来负责不同运行模式下的交互模式。
其中有一个很重要的全局指针GEngine,通过GEngine可以访问各种UE的全局资源,同时GEngine还提供多线程访问能力。
UObject是构成UE世界最基础的物质,所以UObject提供供UE世界运行的最基本的功能:
- Garbage collection:垃圾收集
- Reference updating:引用自动更新
- Reflection:反射
- Serialization:序列化
- Automatic updating of default property changes:自动检测默认变量的更改
- Automatic property initialization:自动变量初始化
- Automatic editor integration:和虚幻引擎编辑器的自动交互
- Type information available at runtime:运行时类型识别
- Network replication:网络复制
Actot可放置在场景中的类,Actor的概念在UE里其实不是某种具象化的3D世界里的对象,而是世界里的种种元素,用更泛化抽象的概念来看,小到一个个地上的石头,大到整个世界的运行规则,都是Actor.
AActor是派生自UObject的一个及其重要的类,AActor在UObject的基础上再进一步提供了:
- Replication:网络复制
- Spawn:动态创建
- Tick:每帧运行
ALevelScriptActor在官方文档中的表述就是ULevelScriptBlueprint生成的类的基类,ULevelScriptBlueprint就是我们最常用的关卡蓝图,ULevelScriptBlueprint继承自UObject,所以ULevelScriptBlueprint的子类是一个多继承的虚继承类,而ALevelScriptActor就为其提供AActor的能力。
在官方文档中有提及默认关卡蓝图是可以通过DefualtGame.ini配置文件替换成自定义关卡蓝图的
由ANavigationObjectBase是APlayerState的基类,和它继承的接口INavAgentInterface可以猜测ANavigationObjectBase应该和网络复制有关
APlayerStart的作用就是记录APawn在游戏开始时生成的Position与Rotation信息,UE设计APlayerStart的初忠就是想让游戏的关卡设十师和场景设计师的工作分离开来,也就解耦合。那么,如果Level中不存在APlayerStart ,APawn 会出生在哪是呢?答案是世界原点(0,0,0)
APawn在AActor的基础上再度添加了:
- 被Controller控制
- PhysicsCollision:物理碰撞
- MovementInput:移动响应接口
由于有了MovementInput接口,APawn拥有了可运动的能力,UE将一个可运动的物体巧妙地划分成了APwan和AController,APawn重点表现在物体,而这个物体具备运动能力,但是自身不具备运动技巧;而AController这是控制APawn运动地大脑,用来控制APawn如何运动,如果把APawn比作是提线木偶,那么AController就是控制木偶运动的线。
到了APawn这一代,AActor的衍化之旅开始衍化出现于玩家间交互的能力,而这之中的佼佼者便是ACharacter。
ACharacter是APwan的特化加强版,在UE世界中可以称之为“人”,ACharacter是一个专门为人形角色定制的APawn,自带CharacterMovement组件,可以使人形角色像人一样行走。
最初始的APawn是最基本的APawn类,只提供APawn的一些基本能力,而没有提供支持这些能力的组件,而在具体实际使用情况中我们使用的APawn应该还需要组合一些其他的能力,以适应不同的场景,如:我们知道APawn可以运动,但在实际场景中我们是要确定这个APawn是因该直立行走还是爬行,是用轮子行驶还是用翅膀飞行,APawn在玩家眼里应该长什么样子,是人还是蛇,是因该左球形碰撞还是应该做方形碰撞,这些都是APawn不具备的能力,这时ADefaultPawn便出现了,ADefaultPawn自带DefualtPawnMovement、CollisionComponent、StaticMeshCompnent三件套,为ADefaultPawn提供了默认的场景表现。
在游戏中存在一种特殊的玩家—观战玩家,这类玩家不需要具体表现形式,只需要一些相机的漫游能力,于是ASpectatorPawn出现了,ASpectatorPawn继承自ADefaultPawn,ASpectatorPawn提供了一个基本的USpectatorPawnMovement(不带重力漫游),并关闭了StaticMesh的显示,碰撞也设置到了“Spectator”通道。
AController就是控制APawn运动的大脑了,ACtroller负责处理一些直接与玩家交互的控制逻辑,AController是从AActor派生的与APawn同级的子类,在UE的设计中,在同一时刻一个AController和一个APawn之间是1:1的关系,AController可以在多个APawn之间通过Possess/UnPossess切换。AController有两种控制APawn的方式,一种是AController直接附在APawn的身上控制APawn的移动,如驾驶汽车,一种是以上帝的视角控制APawn的移动,如控制第三人称的角色。
APlayerController是由AController派生出来专门用于负责玩家交互逻辑的AController,APlayerController提供了:
- Camera管理
- Input输入响应
- UPlayer关联
- HUD显示
- Level切换
- Voice音源监听
AAIController控制NPC(也可以叫AI)的行动逻辑,AAIController与APlayerController完全不同,因为一个NPC不要管理Camera,不需要响应玩家的输入,不需要关联UPlayer,不需要显示HUD,不需要监听音源,只有Level切换可能会在少数情况下需要,AAIController的功能:
- Navigation:自动寻路
- AI Component:用于启动运行行为树,使用黑板数据
- Task系统:让AI去完成一些任务
当然一个游戏中是至少需要一个APlayerController的,但是可以没有AAIController。
AInfo是一些数据保存类的基类,AInfo不需要运动和碰撞,也不需要物理表现,仅仅只是保存数据,所以UE在AInfo中将这些功能都隐藏了,之所以不直接继承自UObject,而继承自AActor是因为游戏数据是需要具备网络复制的能力的,而UObject不具备这个能力
AWordSetting继承自AInfo用来配置和保存一些Level配置,主要用于配置Level的GameMode信息,光照信息,导航系统,声音系统,LOD系统,物理加速度等关卡信息。由此可以知道一个Level对应一个AWordSetting,但是一个AWordSetting可以应用在多个Level上。
AGameMode就是用于配置AWorldSetting中的GameMode属性的。
在UE的设计中AGameMode就是游戏世界的逻辑,及整个游戏的玩法规则,而在实际情况中一个游戏既可以只有一个玩法也可以有多种玩法规则,所以AWordSetting与AGameMode的对应关系也是一个AWorldSetting只能对应一个AGameMode,而一个AGameMode可以对应多个AWorldSetting。那么AGameMode应该负责的逻辑:
- Class登记:记录GameMode中各种类的信息
- Spawn:创建Pawn和PlayerController等
- 游戏进度:游戏暂停重启的逻辑
- 过场动画逻辑
- 多人游戏的步调同步
AGameState用于保存游戏数据,如任务进度,游戏活动等。
APlayerState是一个用于存储玩家状态的类,在一个游戏客户端,尤其是网络游戏客户端中是可以存在多个APlayerState对象的,不同的APlayerState保存不同玩家的状态,同时APlayerState也可以存在于服务器中。APlayerState的生命周期为一整个Level的生命周期。
到这是AActor家族下的几个重要成员的基本功能我们便有了一个大概的了解了,这里我们来捋一下这些成员之间的关系和在UE世界中的地位。
UActorComponent是UE向U3D看齐的一个产物,虽然UE世界有了Actor就有了形形色色的物体生物,但是不同的生物拥有不同的技能,而同一个Actor可以会某个技能也可以不会,这种概念使用组合的方式组合到Actor下是最理想的,于是Component便出现了,UActorComponent直接继承自UObject,与AActor同级,Component既可以嵌套在Actor下,也可以嵌套在其他的Component下,但是需要注意的是,UActorComponent这一级是不提供互相嵌套的能力的,只有到其子类USceneComponent一级才提供互相嵌套能力。
USceneComponent主要提供两大能力,一是Transform,二是SceneComponent的互相嵌套。一般我们直接在Level里创建的Actor都会默认带有一个SceneComponent组件。
UPrimitiveComponent主要提供Actor用于物体渲染和碰撞相关的基础能力。
UMeshComponent由UPrimitiveComponent派生而来,主要提供具体的渲染显示方面的能力。
从名字就可以窥探其功能一二了,UChildComponent在Actor中主要用于链接Actor与Component,提供Component和Actor的嵌套能力。
ULevel可以看作是UE世界的大陆,是AActor的容器,前面提到的ALevelScriptActor便是ULevel默认带有的关卡蓝图,在这个关卡蓝图中编写便是这块大陆的逻辑,同时ULevel也默认带有一个AWorldSetting。
在UE中所有的ULevel互相联系就构成了一个UWorld,ULevel构建UWorld的方式有两种,一种是以SubLevel的形式,像关卡流一样,一个关卡链接下一个关卡,来组成UWorld,一种是每一个ULevel就是这个大地图的UWorld中的一块地图,ULevel之间以相对位置衔接在一起,构成一个大地图来组成这个UWorld。无论是那种构成形式,在一个UWorld中都有一个PersistentLevel,PersistenetLevel就是主Level,是玩家最初始的出生地,这里用的是最初始而不是游戏开始,是因为,现在很多在游戏开始时玩家的出点可能不是PersistentLevel而是上一次玩家离线时的位置。
FWorldContext不对开发者公开,是UE内部用来处理引擎UWorld上下文的类,比如当我们从编辑状态的EditorWorld点击播放切换到PIEWorld即运行状态时,这个过程中EditorWorld到PIEWorld之间的信息交换就是通过FWorldContext实现的。可以说FWorldContext处理的是UWorld级的通信。
UGameInstance可以说是凌驾于所有AActor、UActorComponent、ULevel、UWorld之上的类,通常情况下一个Game中应该只有一个,这里的Game是UEngine中提到的所有World的总和,当然这不是绝对的,对于更高层次的开发者,UE也是提供了多个UGameInstance协同的扩展的。UGameInstance的生命周期就是从游戏进程启动到游戏进程结束。
所以UGameInstance主要处理:
- UWorld、ULevel之间的切换
- UPlayer的创建,这里的UPlayer又和前面的APlayerController有所不同,这一点在后面再介绍。
- 全局配置
- GameMode的切换
从名字就可以略知一二,UNetDriver是UE处理网络同步相关的类,UNetDriver中有两个主要的成员:
class UNetConnection* ServerConnection;
TArray ClientConnections;
ServerConnection是客户端到服务器的连接,ClientConnections数组是服务器到客户端群的连接的数组。而在UNetConnnection中又有一个很重要的成员:
TMap,class UActorChannel*> ActorChannels
ActorChannels是在服务器与客户端完成连接后用于实现Actor同步的对象。
UPlayer即玩家,ULevel可以切换,UWorld可以交替,但是尽管ULevel、UWorld如何变换,玩家还是那个玩家,所以UPlayer是和UGameInstance同一级别的存在,在整个GamePlay架构中UPlayer主要以GameModeBase中的一个属性出现。
在一个单机游戏中UPlayer是唯一的存在,但是在一个网络联级游戏中,表示同一实体的UPlayer即存在于玩家本地的客户端中,同时也存在于其他玩家的多个客户端中,那么玩家的输入就既要作用于本地的APawn上,同时在其他玩家的客户端中的表示这个实体的APawn也要做出响应的反应,于是UE便将UPlayer又派生出了两个子类,ULocalPlayer和UNetConnection。其中ULocalPlayer就是处理本地客户端的输入逻辑的类。
UNetConnection就是处理其他玩家在本地客户端中的APawn的类,所以UNetConnection也是一个玩家。
前面提到了AGameState是一个保存游戏数据的类,这个保存是一个临时保存,所以当游戏程序关闭之后AGameState中数据也就不存在了,而USaveGame就是用来保存存档的类,USaveGame提供游戏数据永久性保存,我们只需要往USaveGame中添加我们要保存的属性字段,就可以直接调用USaveGame的接口直接将游戏数据序列化保存到本地文件中,相当的方便。