Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Unreal随笔系列2: 初始化流程&Gameplay基础类

Unreal随笔系列2: 初始化流程&Gameplay基础类

作者头像
JohnYao
发布于 2023-03-08 07:19:44
发布于 2023-03-08 07:19:44
1.4K00
代码可运行
举报
运行总次数:0
代码可运行

导语

近期排查一个问题时,将Unreal的启动的初始化流程和基础的Gameplay类又review了下。 将相关内容整理,随笔记录下来。

Unreal程序入口点和主循环

Unreal使用C++作为基础开发语言;作为一个引擎,它的代码结构庞杂,功能繁多。但如果从整个计算机体系看,它仅是操作系统的一个进程;作为C++编写的可执行程序,它启动后,代码执行的入口必然是main函数。

Windows环境下,使用Visual Studio调试,我们在FEngineLoop的PreInitPreStartupScreen函数增加断点。可以得到如下堆栈:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    FEngineLoop::PreInitPreStartupScreen(const wchar_t * CmdLine)
    FEngineLoop::PreInit(const wchar_t * CmdLine) 
    EnginePreInit(const wchar_t *) 
    GuardedMain(const wchar_t * CmdLine) 
    GuardedMainWrapper(const wchar_t * CmdLine)
    LaunchWindowsStartup(...) 
    WinMain(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * pCmdLine, int nCmdShow)

在调用栈的最底层,正是所有Windows程序的入口点。

不同平台的程序入口点分别位于如下代码目录。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Engine/Source/Runtime/Launch/Private/${platform}

如果跳过为了可移植性编写的不同操作系统的入口点函数,引擎程序本身的入口点可以认为是GuardedMain。(实现位于Runtime/Launch/Private/Launch.cpp文件)。

GuardedMain函数实现的主要流程如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
GuardedMain( const TCHAR* CmdLine )
{
    <...>
    EnginePreInit
    <...>
    EngineInit
    <...>
    while( !IsEngineExitRequested() )
    {
        EngineTick();
    }   
    <...>
}

这个函数和大多数的服务器程序类似:先执行初始化,然后进入所谓的程序循环,在收到引擎退出的请求后,则终止执行。

在介绍上述流程中的初始化步骤前,我们回顾下:

Gameplay的基础类

Unreal构建游戏玩法的基础类罗列如下。对于有志于引擎,或者Unreal游戏开发的同学,花一周左右的时间,对下面的类及其使用基本耳熟能详。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
UEngine   UGameInstance(Outer)  FWorldContext UWorld(OwningGameInstance) ULevel(OwningWorld)

AGameMode  AGameSession  AGameState

AActor(Outer) UActorComponent

APawn AController

UNetConnection UPlayer

简单的讲, UEngine,UGameInstance,UWorld,ULevel是抽象等级比较高的Gameplay管理类。

UEngine是引擎功能的抽象,负责完成整个逻辑循环的实现。ULevel是游戏对象(AActor)的集合。一般来说,UWorld由一个Persistent Level和若干Streaming Levels构成。对于拥有不同的场景的游戏,比如手游的大厅界面和单局游戏,对应着不同的UWorld。 UGameInstance的抽象层级,介于UEngine和UWorld之间。对于GameEngine来说,基本和UEngine具有相同的生命周期,原生实现仅有一个UGameInsance实例;只有在EdiotrEngine中,原生实现会有多个多个不同的UGameInsance实例。

World创建之后,会创建对应的AGameMode实例负责游戏玩法的状态转移,它会创建AGameSession和AGameState辅助自己完成相应工作。在玩家登录时,它会Spawn出玩家控制的角色ACharacter(或者APawn),以及玩家控制器(AController)。

Unreal引擎初始化过程

上面的陈述比较笼统。读者可以结合下面整理的初始化流程,熟悉下各Gameplay对象初始化的时机。以及彼此之间的关系。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
GuardedMain(Launch.cpp)
    EnginePreInit
        FEngineLoop::PreInit
            FEngineLoop::PreInitPreStartupScreen
                FEngineLoop::AppInit()
                    FCoreDelegates::OnInit.Broadcast()
                        InitUObject()
                            StaticUObjectInit();
                                GObjTransientPkg = NewObject<UPackage>(nullptr, TEXT("/Engine/Transient"), RF_Transient);   
            FEngineLoop::PreInitPostStartupScreen               
    EngineInit
        FEngineLoop::Init
            GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);
            UGameEngine::Init
                GameInstance = NewObject<UGameInstance>(this, GameInstanceClass);
            UGameEngine::Start
                UGameInstance::StartGameInstance() 
                    UEngine::Browse
                        UEngine::LoadMap
                            NewWorld = UWorld::FindWorldInPackage(WorldPackage);
                            WorldContext.World()->SetGameMode(URL);
                                UGameInstance::CreateGameModeForURL
                                    World->SpawnActor<AGameModeBase>(GameClass, SpawnInfo);
                            UWorld::InitializeActorsForPlay  
                                AGameModeBase::InitGame
                                    World->SpawnActor<AGameSession>
                            UWorld::BeginPlay
                                AGameMode::StartPlay()  
                            FCoreUObjectDelegates::PostLoadMapWithWorld.Broadcast
    EngineTick循环
        FEngineLoop::Tick()
            UGameEngine::Tick
                UWorld::Tick
                    UWorld::RunTickGroup
                        FTickTaskManager::RunTickGroup
                            <...>
                                FActorTickFunction::ExecuteTick
                                    AActor::TickActor

这里赘述下几个比较关键的节点

1. PreInit的时候, 会创建TransientPkg。

2. FEngineLoop::Init会首先创建Engine类的实例;在Engine初始化过程中会创建GameInstance类的实例。

3. FEngineLoop::Init其后会调用Engine::Start。这里会执行LoadMap的操作,期间会创建UWorld,并Spawn GameMode。

4. 在LoadMap的最后,会使用PostLoadMapWithWorld事件通知关心该事件的对象。

5. 在完成了初始化,进入Tick循环。

初始化流程的定制

对于业界现有的单局化的游戏开发来说,LoadMap承载的只是单局内的静态资源,对于每局都有一定随机性的游戏设计,需要在LoadMap完成后,继续一些动态的初始化流程。另外为了优化性能,游戏一般会引入预创建的过程。动态初始化和预创建,可以通过对以上流程的理解,安排在合适的时机:比如PostLoadMap,或者放到Tick内。

这些定制化的流程,可能带来游戏流程的变化。比如等待这些定制化流程完成的阶段。比如等待所有玩家登录的阶段。

这里仅做抛砖引玉,读者可以结合自己实际工作,自行思考下定制化的初始化流程实现。

玩家登录后的初始化过程

以上是整个游戏的初始化流程。下面我们看下玩家的初始化流程。

对于联机游戏,玩家登录的初始化流程是在如下堆栈之上完成的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    UWorld::NotifyControlMessage(UNetConnection * Connection, unsigned char MessageType, FInBunch & Bunch)
    UControlChannel::ReceivedBunch(FInBunch & Bunch)
    UChannel::ReceivedSequencedBunch(FInBunch & Bunch)
    UChannel::ReceivedNextBunch(FInBunch & Bunch, bool & bOutSkipAck)
    UChannel::ReceivedRawBunch(FInBunch & Bunch, bool & bOutSkipAck)
    UNetConnection::ReceivedPacket(FBitReader & Reader, bool bIsReinjectedPacket)
    UNetConnection::ReceivedRawPacket(void * InData, int Count)
    [外部代码]  
    Invoke(void(UNetDriver::*)(float))
    UE::Core::Private::Tuple::TTupleBase<TIntegerSequence<unsigned int>>::ApplyAfter(void(UNetDriver::*)(float) &)
    TBaseUObjectMethodDelegateInstance<0,UNetDriver,void __cdecl(float),FDefaultDelegateUserPolicy>::ExecuteIfSafe(float <Params_0>)
    TMulticastDelegate<void __cdecl(float),FDefaultDelegateUserPolicy>::Broadcast(float <Params_0>)
    UWorld::Tick(ELevelTick TickType, float DeltaSeconds)1373    C++
    UGameEngine::Tick(float DeltaSeconds, bool bIdleMode)1831    C++

引擎会在tick中调用NetDriver的命令处理。连接的登录请求包,最终由UWorld::NotifyControlMessage来处理。

其后的玩家创建流程如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
UWorld::NotifyControlMessage
    UWorld::SpawnPlayActor
        AGameModeBase::Login
            SpawnPlayerController
            InitNewPlayer
                UpdatePlayerStartSpot
                    <...>
                        ChoosePlayerStart_Implementation
        AGameModeBase::PostLogin
            HandleStartingNewPlayer
                RestartPlayer
                    <...>
                        AGameModeBase::SpawnDefaultPawnAtTransform_Implementation
                StartMatch

玩家初始化的关键点有两个:

1. 执行GamoMode的Login流程,主要是创建PlayerController,并选择出生点。

2. 执行GamoMode的PostLogin流程,这里会创建玩家控制的Character或者Pawn, 同时根据GameMode状态决定是否可以进入下一状态(StartMatch)。

同样的,玩家的登录流程也可以定制。比如增加玩家鉴权,比如拉取玩家装备。这里也仅作抛砖引玉,不细致展开。

结语

本文对Unreal的主程序入口和Gameplay基础类做了一定剖析,并详细整理了引擎初始化,和玩家登录后的初始化流程。 希望对于做Unreal Gameplay开发的同学,减少相关代码的学习时间,并对定制化的游戏流程设计产生帮助。

随笔系列说明

23年新挖一个《Unreal随笔系列》的坑。所谓随笔,就是研究过程中的一些想法随时记录;细节可能来不及考证,甚至一些想法可能也不太成熟,有失偏颇;希望读者也可以帮忙指正和讨论。这个系列主要求量,希望每个月给自己布置一些研究小课题,争取今年发满12篇。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-02-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Pygame基础9-射击
玩家用鼠标控制飞机(白色方块)移动,按下鼠标后,玩家所在位置出现子弹,子弹匀速向右飞行。
一只大鸽子
2024/04/11
1290
Pygame基础9-射击
Unreal TickFunc调度
Tick在计算机领域并没有很好的中文翻译,英汉词典里的解释是很短的一段时间,或者时钟的一次滴答。
JohnYao
2022/06/29
1.3K0
Unreal TickFunc调度
UE网络通信(四)RPC&移动通信
距离上一次发表《UE网络通信》系列的文章已经过去了一年多。这段时间,UE5.0在2022年4月发布;UE5.1在2022年11月发布。好在新版本,引擎在同步方面尚未做大的变更;之前立的关于RPC,底层协议的写作flag,还是可以继续进行。
JohnYao
2023/04/28
2.8K0
UE网络通信(四)RPC&移动通信
UE4的执行流程和CPU优化
UE4是一个非常庞大的游戏引擎,说是游戏引擎,但其实内部实现的已经和一个小型操作系统差不多了,源码更是海量级别的。在这样海量的源码面前想要搞清楚是怎样运行的本身就不是一件容易的事情,除此外引擎本身是基于多线程设计的,不同线程之间错综复杂的关系更加深了理解引擎的难度。平时在写代码时候,我们也可能更多的只是关注Actor,Component,Level,World以及游戏逻辑怎么写,但很少去研究他们都是怎样运行的,但是如果不了解这些Actor,Component,Level,World,在游戏线程和渲染线程之间是怎样执行的,不清楚内部的运行机制是怎样的,就很容易写出性能糟糕或有各种问题的代码。为了解决这个问题,我整个梳理了一下UE4的大流程,画了一张图,关键点都用颜色标记了出来,让各个环节能够一目了然,这样就可以围绕着这个执行流程,来介绍一些常见的问题和性能优化手段,避免大家写出糟糕的代码。
quabqi
2021/11/04
2.2K0
UE4的执行流程和CPU优化
Unreal随笔系列3: 移动逻辑
书接上回,在随笔系列的第一篇,我介绍了移动输入和物理模拟的大致过程。第一篇的重点是展示以上过程中,Unreal使用的数学,物理知识。
JohnYao
2023/03/12
9960
UE4 ReplicationGraph分析
ReplicationDriverClassName="/Script/ProjectName.ClassName"
小伏羲
2018/12/18
4.6K0
UE4 ReplicationGraph分析
Unreal随笔系列1: 移动实现中的数学和物理
23年新挖一个《Unreal随笔系列》的坑。所谓随笔,就是研究过程中的一些想法随时记录;细节可能来不及考证,甚至一些想法可能也不太成熟,有失偏颇;希望读者也可以帮忙指正和讨论。这个系列主要求量,希望每个月给自己布置一些研究小课题,争取今年发满12篇。
JohnYao
2023/03/08
1.1K0
Unreal随笔系列1: 移动实现中的数学和物理
unreal C++初步介绍
程序员利用C++即可添加基础Gameplay系统,然后设计师可基于这些系统进行构建或利用这些系统为某个特定关卡或游戏本身创建自定义Gameplay。
李小白是一只喵
2021/06/17
1.4K0
unreal C++初步介绍
Swift 反初始化
在类实例被释放的时候,反初始化器就会立即被调用。你可以是用 deinit 关键字来写反初始化器,就如同写初始化器要用 init 关键字一样。反初始化器只在类类型中有效。
赵哥窟
2020/07/28
3230
Unreal随笔系列4:UE4关闭指定平台距离场烘培
我们项目构建了Linux版本的客户端,用于DS的压测。最近一段时间, Unreal Linux Client的构建时间异常的久,所以简单的探究了下Cook的原理。最终通过关闭linux平台下的距离场(Distance Field)烘培,缓解了构建时间的问题。
JohnYao
2023/03/23
5880
Unreal随笔系列4:UE4关闭指定平台距离场烘培
Unreal 01 - LoadMap
FCoreUObjectDelegates::PreLoadMap.Broadcast(URL.Map); 回调事件通知
Reck Zhang
2021/08/11
1.3K0
Swift基础 去初始化
翻译自:https://docs.swift.org/swift-book/LanguageGuide/Deinitialization.html
郭顺发
2023/07/17
1200
【技术总结】UE4中的Subsystem
在游戏开发过程中我们往往需要创建一系列的工具来辅助我们开发,例如UI管理工具,各类导表工具。在UE4.22之前我们只能够自己编写单例,并且自己管理生命周期。或者直接将管理游戏的工具编写进GameInstance中。但是随着代码量的增加,GameInstance将会变得难以维护。在4.22版本发布了之后,我们可以直接将工具写在Subsystem中,让引擎帮我们自动管理工具类的生命周期,不再需要自己维护工具的生命周期或者修改引擎的类(如GameInstance)。
太阳影的社区
2021/10/15
6K0
UE4 GamePlay架构学习篇
现在UE4刚免费不久,网上的资料还很少,有一些UE3的大佬出了一些学习的帖子。通过参考前辈的文章+通过查阅官方文档和官方的模板案例测试得出如下结论,供学习参考:
全栈程序员站长
2022/11/09
1.6K0
UE4 GamePlay架构学习篇
开发 | 傻瓜式操作带你初始化「跳一跳」游戏场景
在上一篇教程里,知晓程序为大家详细讲解了如何创建小游戏「跳一跳」的游戏场景。通过介绍,大家一定对于小游戏的开发有了更进一步的认识。
知晓君
2018/07/25
8450
开发 | 傻瓜式操作带你初始化「跳一跳」游戏场景
论一种模块化的 Minecraft Minigame 游戏架构模型
近一年来,我都在负责一款 Minecraft Minigame 的开发,籍此机会,我总结了一套灵活的,可拓展的,模块化的架构,可以高效的处理游戏主循环的运行。简而言之,这些架构由一些被称之为 Flow, Phase 和 Module 的东西共同组成。要想了解它们,我们需要先从游戏主循环开始…
HikariLan贺兰星辰
2023/01/31
6830
Unreal学习笔记2-绘制简单三角形
之所以写这个绘制简单三角形的实例其实是想知道如何在Unreal中通过代码绘制自定义Mesh,如果你会绘制一个三角形,那么自然就会绘制复杂的Mesh了。所以这是很多图形工作者的第一课。
charlee44
2023/03/07
9830
Unreal学习笔记2-绘制简单三角形
Unreal Engine C# 脚本编写浅谈
随着游戏开发技术的发展,越来越多的游戏引擎开始支持多种编程语言,以满足不同开发者的需求。Unreal Engine 作为一款功能强大的游戏引擎,虽然主要支持 C++ 和蓝图(Blueprint)进行脚本编写,但通过一些插件和工具的支持,也可以使用 C# 进行开发。本文将从基础到进阶,介绍在 Unreal Engine 中使用 C# 编写脚本的一些常见问题、易错点以及如何避免这些问题,并通过代码案例进行详细解释。
Jimaks
2024/11/21
4680
全网首篇? Unreal Iris Replication中文资料
前天看到Unreal 5.1引入了名为Iris的新的同步机制。过去三年一直在做UE4网络层的相关优化,看到这个新的实验特性,还是倍感振奋。在网上搜索了下,并没看到相关的中文资料。在Unreal开发者社区看到了如下文档:
JohnYao
2023/04/01
1.8K0
UE4 Actor生命周期 SpawnActor DestroyActor剖析「建议收藏」
第一部分,从编辑器点击Play开始分析World里面全部的Actor的Spawn流程,分析到调用BeginPlay结束
全栈程序员站长
2022/11/01
3.2K0
相关推荐
Pygame基础9-射击
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验