The Unreal Engine Game Framework: From int main() to BeginPlay

Alex Forsythe
4 Dec 202027:22

Summary

TLDRThe video script delves into the intricacies of the Unreal Engine's game framework, illustrating the process from the game's initialization to the start of gameplay. It explains the concept of the game loop and how Unreal Engine abstracts it through classes like GameMode and PlayerController. The script outlines the engine's initialization stages, including module loading, engine object creation, and map loading, leading to the game's start with the BeginPlay event. It emphasizes the engine's flexibility for both single-player and multiplayer game development, highlighting the importance of understanding the framework for clean and efficient game design. The summary serves as a guide for developers to leverage Unreal Engine's powerful tools and systems for creating engaging game experiences.

Takeaways

  • 🔄 **Game Loop Concept**: The fundamental concept in game programming is the game loop, which involves initialization, processing input, updating game state, and rendering to screen.
  • 🚀 **Unreal Engine Workflow**: In Unreal Engine, you don't deal directly with the game loop. Instead, you work with subclasses like GameMode and functions such as InitGame, BeginPlay, or Tick.
  • 🛠️ **Extensibility and Flexibility**: Unreal Engine offers power and flexibility, being open-source and designed to be extended through various means, including subclassing and overriding functions.
  • 🧩 **GameFramework Overview**: Understanding classes like GameMode, GameState, PlayerController, Pawn, and PlayerState is crucial for leveraging the engine's full capabilities.
  • 📚 **Source Code Exploration**: Gaining familiarity with the engine involves looking at its source code to understand how the game boots up and the sequence of operations.
  • 🔧 **Engine Initialization Process**: The engine runs thousands of lines of code to set up global state and initialize systems before reaching higher-level abstractions.
  • 🌐 **Module System**: The engine is divided into source modules, which are loaded in phases to manage dependencies and ensure only necessary parts are loaded for a given configuration.
  • 🏗️ **Game Instance and World Creation**: The engine creates a GameInstance, GameViewportClient, and LocalPlayer before loading the game map and initializing the world.
  • 👾 **Actor Initialization and Registration**: Actors and their components are registered, initialized, and brought up for play during the LoadMap process.
  • 👥 **Player Login and Pawn Possession**: Player login is handled by the GameMode, which spawns PlayerControllers and PlayerStates, leading to Pawn possession and player setup in the game world.
  • 🔄 **Game Loop and Map Loading**: The game loop manages the entire process from initialization to shutting down, with map loading being a significant part of the game's startup sequence.

Q & A

  • What is the fundamental concept in game programming that the script begins with?

    -The fundamental concept in game programming that the script begins with is the game loop, which involves initialization, processing input, updating the game world state, and rendering the results to the screen.

  • How does Unreal Engine abstract the game loop for developers?

    -Unreal Engine abstracts the game loop by allowing developers to start with defining a GameMode subclass and overriding functions like InitGame, BeginPlay, or Tick, rather than dealing with the main function and the game loop directly.

  • What are some of the key classes in Unreal Engine's GameFramework?

    -Some of the key classes in Unreal Engine's GameFramework include GameMode, GameState, PlayerController, Pawn, and PlayerState.

  • How does the engine handle the transition from the entry point to running game code?

    -The engine handles the transition from the entry point to running game code by initializing various systems and setting up global state through thousands of lines of code, which, while complex, are necessary for the engine to function correctly.

  • What is the role of FEngineLoop in the Unreal Engine?

    -FEngineLoop is a class that implements the main loop for the Unreal Engine. It manages the PreInit stage, full initialization of the engine, and then ticks every frame until the program is ready to exit.

  • How does the module system in Unreal Engine help with managing dependencies and configurations?

    -The module system in Unreal Engine helps manage dependencies and configurations by splitting the engine into different source modules, allowing for essential systems to be initialized first and ensuring that only the necessary modules for a given configuration are loaded.

  • What is the purpose of a CDO (Class Default Object) in Unreal Engine?

    -A CDO (Class Default Object) in Unreal Engine serves as a record of a class in its default state and acts as a prototype for further inheritance. It is used to allocate a default instance of a class and run its constructor, passing in the CDO of the parent class as a template.

  • What is the significance of the UEngine class in Unreal Engine's initialization process?

    -The UEngine class is significant in Unreal Engine's initialization process as it is responsible for creating an instance of the GameEngine class, initializing it, and starting the game. It also contains functions for loading maps and browsing to URLs.

  • How does the LoadMap function contribute to the game's initialization?

    -The LoadMap function contributes to the game's initialization by finding and loading the map package, initializing the World, registering components, initializing GameMode and GameSession, and finally calling BeginPlay on all actors, completing the game's startup sequence.

  • What is the difference between a PlayerController and a Pawn in Unreal Engine?

    -A PlayerController in Unreal Engine represents the player within the game world and is responsible for controlling the player's interactions. A Pawn, on the other hand, is a specialized type of actor that can be possessed by a Controller and represents the in-world entity that the player or AI controls.

  • Why is it important to understand the lifetime of different game framework actors in Unreal Engine?

    -Understanding the lifetime of different game framework actors is important because it determines when and how these actors are created, used, and destroyed in the game. This knowledge helps developers manage resources effectively and ensures that game state is maintained correctly across different game phases.

  • How can developers extend functionality in Unreal Engine beyond class inheritance?

    -Developers can extend functionality in Unreal Engine beyond class inheritance by binding callback functions to delegates that represent specific engine events, or by using the subsystem feature which allows for the automatic creation of instances of custom classes tied to the lifetime of corresponding objects.

Outlines

00:00

🎮 Game Loop and Unreal Engine's Extensibility

The first paragraph introduces the concept of the game loop, a fundamental aspect in game programming where the game initializes, runs a loop for gameplay, and cleans up after shutdown. It contrasts the direct handling of the game loop with the approach in Unreal Engine, where developers work with a GameMode subclass and override functions like InitGame. The paragraph also touches on the engine's open-source nature and extensibility, the GameFramework's key classes, and the importance of understanding the engine's source code for deeper familiarity.

05:02

📚 Engine Initialization and Module Loading

The second paragraph delves into the engine's initialization process, explaining the PreInit stage where modules are loaded based on their LoadingPhase. It details how the engine loop transitions from PreInit to the Init function, which involves creating a UEngine instance and preparing the game for map loading. The paragraph also discusses the UEngine class's responsibilities, including managing the game's map and handling URL browsing for server connections or local map loads.

10:03

🌐 World Creation and Game Framework Setup

The third paragraph describes the process of creating a UWorld and setting up the game framework. It explains how the engine loads a map package, initializes the world, and sets up systems like physics and AI. The paragraph outlines the steps for initializing actors for play, including registering components, spawning gameplay actors like GameMode, and setting up the player's environment. It also discusses the lifetime of different game objects and the transition from the process lifetime to the game map lifetime.

15:05

👤 Player Login and Pawn Possession

The fourth paragraph focuses on the player login process and the creation of a PlayerController. It details how the GameMode handles login requests, the spawning of a PlayerController and PlayerState, and the networking initialization. The paragraph also explains the concept of Pawn possession, where a PlayerController takes control of a Pawn actor, and how the game mode can customize player startup, including spawning and restarting players.

20:08

🔄 Game Loop Continuation and Engine Ticking

The fifth paragraph continues the discussion on the game loop, emphasizing the initialization stage and the transition into the game's running state. It outlines the steps from routing the BeginPlay event to the game being fully up and running. The paragraph also provides a review of the game framework components, such as GameModeBase and GameStateBase, and the utility of the Character class for movement and multiplayer games.

25:09

🤔 Data Structure and Engine Extension Methods

The sixth and final paragraph emphasizes the importance of critical thinking in data structure and object interaction for game development. It highlights alternative methods for extending engine functionality, such as binding callback functions to delegates or using the subsystem feature for modular functionality. The paragraph concludes with an encouragement to understand the engine's design decisions and a call for support on Patreon for continued content creation.

Mindmap

Keywords

💡Game Loop

The game loop is a fundamental concept in game programming that represents the core structure of a video game. It is responsible for the continuous operation of the game, processing input, updating the game state, and rendering the results to the screen. In the context of the video, the game loop is contrasted with the way Unreal Engine abstracts this process, allowing developers to focus on game logic without directly managing the loop.

💡GameMode Subclass

In Unreal Engine, a GameMode subclass is a custom class that developers define to create a specific type of game. It is a subclass of the UGameModeBase class and is used to override functions like InitGame to initialize the game. The video explains that by defining a GameMode subclass, developers can add their own logic to the game without directly interacting with the game loop.

💡Actor and Component Classes

Actor and Component classes in Unreal Engine are the building blocks for game objects and their behaviors. Actors represent objects in the game world, while Components define the functionality of those objects. Developers override functions like BeginPlay or Tick in these classes to implement custom logic. The video emphasizes the importance of these classes for adding logic to the game without managing the underlying game loop.

💡Game Framework

The Game Framework in Unreal Engine is a set of classes and systems that provide the foundation for game logic and gameplay mechanics. It includes classes like GameMode, GameState, PlayerController, Pawn, and PlayerState. The video discusses the Game Framework as a powerful and flexible system that allows developers to understand and interact with the engine's core functionalities.

💡Source Modules

Source modules in Unreal Engine are defined units of code that can be loaded during the engine's initialization process. They are used to organize the codebase and manage dependencies between different parts of the engine. The video explains that the engine's PreInit phase loads essential modules first, followed by higher-level modules and game-specific code.

💡UObject Classes

UObject is a base class in Unreal Engine from which most game-related classes are derived. When a module is loaded, the engine registers any UObject classes defined in that module, making the reflection system aware of those classes and creating a Class Default Object (CDO) for each. The video highlights the importance of UObject classes in the engine's initialization process.

💡GameInstance

The GameInstance in Unreal Engine is an object that represents the game data that persists across different levels or maps. It is created during engine initialization and is used to preload assets and maintain game state. The video describes the GameInstance as a key component in the engine's startup sequence, where it helps manage the game's lifecycle.

💡LoadMap Function

The LoadMap function in Unreal Engine is used to load a new map or level into the game. It is a complex process that involves cleaning up the current world, loading a new map package, and initializing the new world. The video details the LoadMap process as a critical step in transitioning from the engine's initialization to the game's runtime.

💡PlayerController

A PlayerController in Unreal Engine is an actor that represents the player within the game world. It is responsible for handling player input and controlling a Pawn, which is the in-world representation of the player. The video explains that the PlayerController is spawned for each LocalPlayer during the game's initialization and is essential for player interaction in the game.

💡Pawn

A Pawn in Unreal Engine is a specialized type of actor that can be controlled by a PlayerController or an AIController. It represents the physical presence of a player or AI in the game world. The video discusses the Pawn as a key element in the game's framework, where it is spawned and possessed by the PlayerController during the player's login process.

💡GameSession

The GameSession in Unreal Engine is a server-only actor that manages the state of an online game session. It is responsible for approving login requests and interfacing with online services. The video describes the GameSession as a critical component for multiplayer games, where it helps manage the game's online functionality.

Highlights

The game loop is a fundamental concept in game programming, consisting of an initialization phase, a main loop for processing input, updating game state, and rendering, followed by cleanup when the game shuts down.

Unreal Engine abstracts the game loop, allowing developers to focus on defining game modes, actors, and components without directly handling the loop.

The engine is extensible and open-source, offering power and flexibility to programmers through its game framework, which includes classes like GameMode, GameState, PlayerController, Pawn, and PlayerState.

Unreal Engine's source code can be examined to understand the game's boot-up process, which involves a complex initialization sequence and management of different systems and threads.

The FEngineLoop class implements the main loop for the engine, with stages including PreInit, full initialization, and per-frame ticking until exit.

During PreInit, the engine loads essential modules and sets up global state, with the process involving a lot of behind-the-scenes complexity.

The engine's module system ensures manageable dependencies and selective loading of modules based on platform and configuration.

The UEngine class, part of the Engine module, is responsible for high-level engine functionalities like loading maps and managing the game instance.

The GameInstance, GameViewportClient, and LocalPlayer are core objects created during engine initialization, representing the game's project-specific functionality, user interface, and the player, respectively.

The LoadMap function is central to game startup, involving loading the map package, initializing the world, and setting up game mode and session actors.

The lifetime of game objects is divided into before and after the LoadMap call, with different objects persisting across map transitions.

GameMode, GameState, and PlayerController are server-authoritative and are tied to the game's state and player experiences.

Player login and joining processes are managed by the GameMode, which handles login requests, player spawning, and initialization.

The Pawn class represents a special type of actor that can be controlled by a PlayerController, with the ability to possess and drive in-game characters.

Unreal Engine supports networked multiplayer games out of the box, with built-in functionalities for online integration, login requests, and network replication.

The Game Framework is opt-in for developers, allowing for a clean design when used as intended, and can be extended through inheritance or by binding to engine delegates.

Unreal Engine 4.22 introduces a subsystem feature for adding modular functionality, allowing for cleaner and more maintainable code when extending engine capabilities.

The Character class, a specialized type of Pawn, includes features like movement replication, animation root motion, and integrated navigation and pathfinding, providing a solid foundation for player and AI movement.

Transcripts

play00:00

One of the most fundamental concepts  in game programming - and one of the  

play00:04

simplest - is the idea of the game loop.

play00:07

When your program runs, you do some initialization

play00:09

to set things up, and then you run a loop  for as long the player wants to keep playing:  

play00:13

each frame, you process input, you  update the state of the game world,  

play00:16

and you render the results to the screen.  

play00:18

When the player shuts down the game,  you do some cleanup, and you're done.  

play00:22

But if you're writing game code in Unreal Engine,  you're not dealing with a game loop directly.

play00:26

You don't start at the main function, you  start by defining a GameMode subclass and  

play00:30

overriding a function like InitGame. Or you're  writing one-off Actor and Component classes,  

play00:35

and you override their BeginPlay or  Tick functions to add your own logic. 

play00:40

And that's really all you have to do  at a minimum: the Engine takes care of  

play00:43

everything else for you, which is exactly  what you want when you're starting out. 

play00:47

But Unreal also offers you a lot of  power and flexibility as a programmer:

play00:51

the Engine is open-source, of course,  but it's also designed to be extensible  

play00:55

in a number of different ways. And even if  you're a beginner, before long you'll want  

play00:58

to have a decent understanding of the Engine's  GameFramework: classes like GameMode, GameState,  

play01:03

PlayerController, Pawn, and PlayerState.

play01:06

And one of the best ways to get more familiar

play01:08

with the Engine is to look at its source  code and see how it boots up your game.  

play01:14

If you crack open the Unreal Engine codebase  and find the main function - that is,  

play01:17

the entry point of the program - it may be  difficult to find your way from that point  

play01:21

to where your game code actually runs.

play01:23

There are lots of different systems at play,

play01:25

there's indirection for supporting  different platforms, there's a whole  

play01:28

lot of conditional compilation going on  to support different build configurations,  

play01:32

there are separate game and render threads, and  there are object-oriented abstractions built on  

play01:36

top of the core "game loop" functionality to  make all of that complexity manageable. 

play01:42

And if you start looking at the  code that initializes the Engine,  

play01:44

you might find some scary-looking stuff.

play01:47

When the Engine starts up, before it gets

play01:49

into those higher-level abstractions, it runs  thousands of lines of code to do lots and lots

play01:53

of little things that set up global state and  initialize various systems, and it's all kind  

play01:57

of gross to look at, but it's an ugly truth in  any kind of software engineering that if it's  

play02:01

long and it's complicated and it was written 20  years ago and it works,

play02:05

you don't touch it.

play02:06

And honestly, some messy complexity is  kind of inevitable at this stage.

play02:10

It's like you're witnessing the first few  moments after the Big Bang: there's tons of

play02:13

stuff happening, and a lot of systems overlapping  each other, in a very small surface area.

play02:18

By the time you get to InitGame or BeginPlay  - and game code that you've written,

play02:22

the universe has expanded and things  have settled into a more ordered form.

play02:26

But I think it can be instructive to cut  through the chaos and look at how the engine

play02:30

gets from the entry point of the program  to actually running your game code.

play02:35

It all begins in the Launch module, where  you'll find different main functions

play02:38

defined for different platforms.

play02:41

Eventually, they all find their way to this GuardedMain function in Launch.cpp.

play02:46

If we squint a little, and maybe cut out  

play02:48

some of this extraneous code, we  can see a basic game loop here.

play02:51

The main loop for the Engine is implemented  in a class called FEngineLoop.

play02:55

We can see that the engine loop has a PreInit  stage, then the engine gets fully initialized,

play02:59

and then we tick every frame  until we're ready to exit.

play03:03

Let's break down what happens  in these function calls.

play03:06

PreInit is where most of  the modules are loaded.

play03:09

When you make a game project or a  plugin that has C++ source code,

play03:12

you define one or more source modules  in your .uproject or .uplugin file,

play03:17

and you can specify a LoadingPhase to  dictate when that module will be loaded.

play03:22

The Engine is split into  different source modules as well.

play03:25

Some modules are more essential than others, and some are only loaded on certain platforms  

play03:29

or in certain situations -  so a module system helps

play03:32

to make sure that the dependencies between  different parts of the codebase are manageable,

play03:35

and it makes sure that we can just load  what we need for any given configuration.

play03:39

When the engine loop begins its PreInit phase,  it loads up some low-level Engine modules

play03:43

so that the essential systems are initialized  and the essential types are defined.

play03:48

Then, if your project or any enabled plugins have

play03:50

source modules that are in these early  loading phases, those are loaded next.

play03:55

After that, the bulk of higher-level  Engine modules are loaded.

play03:59

After that, we come to the default point where  project and plugin modules are loaded.

play04:03

This is typically the point where your  game's C++ code is first injected

play04:07

into what was previously just a  generic instance of Unreal Engine.

play04:10

Your game module comes into being at a point  where all the essential Engine functionality

play04:14

has been loaded and initialized, but before  any actual game state has been created.

play04:20

So what happens when your module is loaded?

play04:22

First, the Engine registers any UObject classes that are defined in that module.

play04:26

This makes the reflection system  aware of those classes,

play04:29

and it also constructs a CDO, or class  default object, for each class.

play04:34

The CDO is a record of your class in  its default state, and it serves as

play04:38

a prototype for further inheritance.

play04:40

So if you've defined a custom Actor type,

play04:42

or a custom Game Mode, or  anything declared with UCLASS

play04:45

in front of it; the engine loop allocates  a default instance of that class,

play04:49

then runs its constructor, passing in the  CDO of the parent class as a template.

play04:54

This is one of the reasons why the constructor  shouldn't contain any gameplay-related code:

play04:58

it's really just for establishing the universal  details of the class, not for modifying

play05:02

any particular instance of that class.

play05:05

After all your classes are registered, the

play05:07

engine calls your module's StartupModule function,  which is matched with ShutdownModule, giving you a

play05:12

chance to handle any initialization that needs  to be tied to the lifetime of the module.

play05:17

So at this point, the Engine loop has loaded all  the required engine, project, and plugin modules,

play05:22

it's registered classes from those modules, and  it's initialized all the low-level systems that

play05:26

need to be in place. That finishes the PreInit  stage, so we can move onto the Init function.

play05:32

The Engine loop's Init function  is comparatively straightforward.

play05:35

If we simplify it just a little, we can see that  it hands things off to a class called UEngine.

play05:40

Prior to this point, when I've said  "engine," we've been talking about the

play05:43

engine with a lowercase e: basically,  the executable that we're starting up,

play05:47

consisting of code that we  didn't write ourselves. 

play05:50

Here we're introducing THE Engine, capital-E  Engine. The engine is a software product,

play05:56

and it contains a source module called Engine,  and in that module is a header called Engine.h,

play06:00

and in that header is defined a class  called UEngine, which is implemented in

play06:04

both UEditorEngine and UGameEngine flavors.

play06:07

During the Init phase for a game, FEngineLoop

play06:10

checks the Engine config file to figure out  which GameEngine class should be used.

play06:15

Then it creates an instance of that class and  enshrines it as the global UEngine instance,

play06:20

accessible via the global variable GEngine,  which is declared in Engine/Engine.h.

play06:26

Once the Engine is created, it's  initialized, which we'll have more

play06:29

to say about in just a second.

play06:31

When that's done, the engine loop

play06:32

fires a global delegate to indicate  that the Engine is now initialized,

play06:35

and then it loads any project or plugin modules  that have been configured for late loading.

play06:40

Finally, the Engine is started,  and initialization is complete.

play06:44

So what does the Engine class actually  do? It does a lot of things, but its

play06:48

main responsibility lies in this set of big, fat  functions here, including Browse and LoadMap.

play06:53

We've looked at how the process boots up  and gets all the engine systems initialized,

play06:57

but in order to get into an actual game and  start playing, we have to load into a map,

play07:02

and it's the UEngine class that  makes that happen for us.

play07:05

The Engine is able to Browse to a URL, which can  represent either a server address to connect to as

play07:10

a client, or the name of a map to load up locally.  URLs can also have arguments added onto them.

play07:16

When you set a default map in your  project's DefaultEngine.ini file,

play07:20

you're telling the Engine to browse to  that map automatically when it boots up.

play07:24

Of course, in development builds, you  can also override that default map by

play07:27

supplying a URL at the command-line, and you  can also use the open command to browse to a

play07:31

different server or map during gameplay.

play07:34

So let's look at Engine initialization.

play07:36

The Engine initializes itself before the  map is loaded, and it does so by creating

play07:40

a few important objects: a GameInstance, a  GameViewportClient, and a LocalPlayer.

play07:46

You can think of the LocalPlayer as representing  the user who's sitting in front of the screen,

play07:50

and you can think of the viewport client  as the screen itself: it's essentially

play07:54

a high-level interface for the rendering,  audio, and input systems, so it represents

play07:58

the interface between the user and the Engine.

play08:01

The UGameInstance class was added in Unreal 4.4,

play08:04

and it was spun off from the  UGameEngine class to handle

play08:06

some of the more project-specific functionality  that was previously handled in the Engine.

play08:11

So after the Engine is initialized,  we have a GameInstance,

play08:14

a GameViewportClient, and a LocalPlayer.

play08:17

Once that's done, the game is ready to start:

play08:19

this is where our initial call to LoadMap occurs.  By the end of the LoadMap call, we'll have a

play08:24

UWorld that contains all the actors that were  saved into our map, and we'll also have a handful

play08:29

of newly-spawned actors that form the core of the  GameFramework: that includes a game mode, a game

play08:35

session, a game state, a game network manager, a  player controller, a player state, and a pawn.

play08:41

One of the key factors that separates  these two sets of objects is lifetime.

play08:45

At a high level, there are two different  lifetimes to think about: there's everything

play08:50

that happens before a map is loaded, and then  there's everything that happens after.

play08:54

Everything that happens before LoadMap is  tied to the lifetime of the process.

play08:59

Everything else - things like GameMode, GameState,  and PlayerController - are created after the map

play09:03

is loaded, and they only stick around for  as long as you're playing in that map.

play09:07

The engine does support what it calls "seamless  travel", where you can transition to a different

play09:11

map while keeping certain actors intact.

play09:13

But if you straight-up browse to a new map,

play09:15

or connect to a different server, or back out  to a main menu - then all actors are destroyed,

play09:20

the world is cleaned up, and these classes are  out of the picture until you load another map.

play09:24

So let's look at what happens in  LoadMap. It's a complicated function,

play09:28

but if we pare it back to the essentials,  it's not that hard to follow.

play09:32

First the engine fires a global delegate to  indicate that the map is about to change.

play09:36

Then, if there's already a map loaded, it cleans  up and destroys that world. We're mostly concerned

play09:41

with initialization right now, so we'll just  wave our hand at that. Long story short,

play09:45

by the time we get here, there's no World.

play09:47

What we do have, though, is a World Context.

play09:50

This object is created by the Game Instance  during Engine initialization, and it's

play09:54

essentially a persistent object that keeps track  of whichever world is loaded up at the moment.

play09:58

Before anything else gets loaded, the GameInstance  has a chance to preload any assets that it might

play10:03

want, and by default, this doesn't do anything.

play10:05

Next we need to get ourselves a UWorld.

play10:08

If you're working on a map in the editor,  the editor has a UWorld loaded into memory,

play10:12

along with one or more ULevels, which contain  the Actors you've placed. When you save your

play10:16

persistent level, that World, its Level, and  all its Actors, get serialized to a map package,

play10:21

which is written to disk as a .umap file.

play10:24

So during LoadMap, the engine finds that map

play10:26

package and loads it. At this point, the World,  its persistent level, and the actors in that

play10:31

level - including the WorldSettings -  have been loaded back into memory.

play10:37

So we have a World, and now  we have to initialize it.

play10:40

The Engine gives the World a reference to the  GameInstance, and then it initializes a global

play10:45

GWorld variable with a reference to the World.

play10:48

Then the World is installed into the WorldContext,  it has its world type initialized - to Game,

play10:53

in this case - and it's added to the root set,  which prevents it from being garbage collected.

play10:58

InitWorld allows the world to set up systems  like physics, navigation, AI, and audio.

play11:04

When we call SetGameMode, the  World asks the GameInstance

play11:07

to create a GameMode actor in the world.

play11:09

Once the GameMode exists, the Engine fully loads

play11:12

the map, meaning any always-loaded sublevels are  loaded in, along with any referenced assets.

play11:18

Next, we come to InitializeActorsForPlay.  This is what the Engine refers to as

play11:22

"bringing the world up for play."

play11:25

Here, the World iterates over all  actors in a few different loops.

play11:29

The first loop registers all actor components  with the world. Every ActorComponent within

play11:34

every Actor is registered, which does three  important things for the component:

play11:38

First, it gives it a reference to the  world that it's been loaded into.

play11:41

Next, it calls the component's  OnRegister function, giving it a

play11:44

chance to do any early initialization.

play11:47

And, if it's a PrimitiveComponent

play11:49

when all is said and done, after registration  the component will have a FPrimitiveSceneProxy

play11:53

created and added to the FScene, which is  the render thread's version of the UWorld.

play11:59

Once components have been registered, the  World calls the GameMode's InitGame function.

play12:03

That causes the GameMode to  spawn a GameSession actor.

play12:07

After that, we have another loop  where the world goes level-by-level,

play12:10

and has each level initialize all its actors.  That happens in two passes. In the first pass,

play12:15

the Level calls the PreInitializeComponents  function on each Actor. This gives Actors a chance

play12:20

to initialize themselves fairly early, at a point  after their components are registered but before

play12:25

their components have been initialized.

play12:27

The GameMode is an actor like any other,

play12:29

so its PreInitializeComponents  function is called here too.

play12:33

When that happens, the GameMode spawns a GameState  object and associates it with the World, and it

play12:38

also spawns a GameNetworkManager, before finally  calling the game mode's InitGameState function.

play12:44

Finally, we finish by looping over all actors  again, this time calling InitializeComponents,

play12:50

followed by PostInitializeComponents.

play12:53

InitializeComponents loops over all the Actor's components and checks two things:

play12:57

If the component has bAutoActivate enabled, then the component will be activated.

play13:02

And if the component has bWantsInitializeComponent enabled,

play13:05

then its InitializeComponent function will be called.

play13:09

PostInitializeComponents is the earliest point  where the actor is in a fully-formed state,

play13:14

so it's a common place to put code that  initializes the actor at the start of the game.

play13:20

At this point, our LoadMap call is nearly done:

play13:23

all Actors have been loaded and initialized,  the World has been brought up for play,

play13:26

and we now have a set of actors used to  manage the overall state of the game:

play13:31

GameMode defines the rules of the game, and it  spawns most of the core gameplay actors.

play13:35

It's the ultimate authority for what happens during  gameplay, and it only exists on the server.

play13:40

GameSession and GameNetworkManager  are server-only as well.

play13:44

The network manager is used to configure things  like cheat detection and movement prediction.

play13:48

And for online games, the GameSession  approves login requests, and it serves

play13:52

as an interface to the online service  (like Steam or PSN, for example).

play13:57

The GameState is created on the server, and  only the server has the authority to change it,

play14:02

but it's replicated to all clients: so it's  where you store data that's relevant to the

play14:06

state of the game, that you want all  players to be able to know about.

play14:10

So now the world has been fully initialized, and  we have the game framework actors that represent

play14:14

our game. All we're missing now are the game  framework actors that represent our player.

play14:20

Here, LoadMap iterates over all the LocalPlayers  present in our GameInstance: typically there's

play14:25

just one. For that LocalPlayer, it calls the  SpawnPlayActor function. Note that "PlayActor"

play14:32

is interchangeable with "PlayerController" here:  this function spawns a PlayerController.

play14:36

LocalPlayer, as we've seen, is the  Engine's representation of the player,

play14:41

whereas the PlayerController is the representation  of the player within the game world.

play14:45

LocalPlayer is actually a specialization of the  base Player class. There's another Player class

play14:50

called NetConnection which represents a player  that's connected from a remote process.

play14:55

In order for any player to join the game,  regardless of whether it's local or remote,

play14:58

it has to go through a login process.

play15:01

That process is handled by the GameMode.

play15:04

The GameMode's PreLogin function is only called for remote connection attempts:

play15:08

it's responsible for approving or rejecting the login request.

play15:12

Once we have the go-ahead to add the player  into the game, either because the remote

play15:16

connection request was approved or because  the player is local, Login gets called.

play15:20

The Login function spawns a PlayerController  actor and returns it to the World.

play15:25

Of course, since we're spawning an actor  after the world has been brought up for play,

play15:28

that actor gets intialized on spawn.  That means our PlayerController's

play15:33

PostInitializeComponents function gets called,  and it in turn spawns a PlayerState actor.

play15:39

The PlayerController and PlayerState are similar  to the GameMode and GameState in that one is the

play15:44

server-authoritative representation of the  game (or the player), and the corresponding

play15:48

state object contains the data that everyone  should know about the game (or the player).

play15:54

Once the PlayerController has been spawned,

play15:56

the World fully initializes it for networking  and associates it with the Player object.

play16:01

With all that done, the game mode's PostLogin  function gets called, giving the game a chance

play16:05

to do any setup that needs to happen  as a result of this player joining.

play16:08

By default, the game mode will attempt to spawn a  Pawn for the new PlayerController on PostLogin.

play16:15

A Pawn is just a specialized type of actor  that can be possessed by a Controller.

play16:20

PlayerController is a specialization  of the base Controller class,

play16:24

and there's another subclass called AIController  that's used for non-player characters.

play16:29

This is a longstanding convention in Unreal: if  you have an actor that moves around the world

play16:34

based on its own autonomous decision-making  process - whether that's a human player making

play16:39

decisions and translating them into raw inputs,  or an AI making higher-level decisions about

play16:43

where to go and what to do - then you typically have two actors.

play16:46

The Controller represents the intelligence driving the actor,

play16:50

and the Pawn is  just the in-world representation of the actor.

play16:54

So when a new player joins the game, the  default GameMode implementation spawns a

play16:57

Pawn for the new PlayerController to possess.

play17:01

The game framework does also support spectators:

play17:03

your PlayerState can be configured to indicate that the player should spectate,

play17:06

or you can configure the GameMode to start all  players as spectators initially. In that case,

play17:10

the GameMode won't spawn a Pawn, and instead  the PlayerController will spawn its own

play17:13

SpectatorPawn that allows it to fly around  without interacting with the game world.

play17:17

Otherwise, on PostLogin the game mode will do  what it calls "restarting the player." Think

play17:22

of "restarting" in the context of a multiplayer  shooter: if a player gets killed, their Pawn is

play17:27

dead - it's no longer being controlled; it just  hangs around as a corpse until it's destroyed.

play17:32

But the PlayerController is still around,  and when the player's ready to respawn,

play17:35

the game needs to spawn a new Pawn for them. So that's what RestartPlayer does:  

play17:40

given a PlayerController, it'll find an actor  representing where the new Pawn should be spawned,

play17:44

and then it'll figure out which Pawn class to  use, and it'll spawn an instance of that class.

play17:49

By default, the game mode looks through  all the PlayerStart actors that have been

play17:53

placed in the map and picks one of them. But  all of this behavior can be overridden and

play17:57

customized in your own GameMode class.

play18:00

In any event, once a Pawn has been spawned,

play18:02

it'll be associated with the PlayerController,  and the PlayerController will possess it.

play18:11

Now, back in LoadMap, we've got everything ready  for the game to actually start. All that's left

play18:16

to do is route the BeginPlay event. The Engine  tells the World, the World tells the GameMode,

play18:21

the GameMode tells the WorldSettings, and  the WorldSettings loops over all actors.

play18:26

Every Actor has its BeginPlay function called,  which in turn calls BeginPlay on all components,

play18:31

and the corresponding BeginPlay  events are fired in Blueprints.

play18:35

With all that done, the game is fully  up and running, LoadMap can finish up,

play18:38

and we've made it into our game loop.

play18:44

Let's run through that one more  time, quickly, just to review.

play18:48

When we run our game in its final,  packaged form, we're running a process.

play18:52

The entry point of that  process is a main function,

play18:54

and the main function runs the engine loop.

play18:57

The engine loop handles initialization,

play18:59

then it ticks every frame, and when  it's done, it shuts everything down.

play19:04

Right now we're mostly concerned with  what happens during initialization.

play19:08

The first point where your project or plugin code  runs is going to be when your module is loaded.

play19:12

That can happen at a number of points,  depending on the LoadingPhase, but typically

play19:15

it happens toward the end of PreInit.

play19:18

When your module is loaded, any UObject

play19:20

classes get registered, and default objects  get initialized via the constructor. Then your

play19:24

module's StartupModule function is called, and this is the first place where you might  

play19:28

hook into delegates to set up other  functions to be called later.

play19:32

The Init stage is where we start  setting up the Engine itself.

play19:35

In short, we create an Engine object, we  initialize it, and then we Start the game.

play19:40

To initialize the Engine, we create a  GameInstance and a GameViewportClient,

play19:43

and then we create a LocalPlayer and  associate it with the GameInstance.

play19:47

With those essential objects in place,  we can start loading up the game.

play19:51

We figure out which map to  use, we browse to that map,

play19:53

and we let the GameInstance  know when that's finished.

play19:56

The rest of our startup process  happens in the LoadMap call.

play19:59

First we find our map package, then we  load it: this brings any actors placed

play20:03

into the persistent level into memory, and it  also gives us a World and a Level object.

play20:07

We find that World, we give it a reference to the  GameInstance, we initialize some systems in the

play20:11

World, and then we spawn a GameMode Actor.

play20:14

After that, we fully load the map,

play20:16

bringing in any always-loaded sublevels  and any assets that need to be loaded.

play20:20

With everything fully loaded, we start  bringing the world up for play.

play20:24

We first register all components  for every actor in every level...

play20:28

And then we initialize the GameMode, which  in turn spawns a GameSession actor.

play20:33

And then we initialize all  the Actors in the world.

play20:35

First, we call PreInitializeComponents  on every actor in every level:

play20:39

when this happens for the  GameMode, it spawns a GameState,

play20:42

and a GameNetworkManager, and then  it initializes the GameState.

play20:46

Then, in another loop, we initialize  every actor in every level: this

play20:49

calls InitializeComponent (and potentially  Activate) for all components that need it,

play20:54

and then our actors become fully-formed.

play20:56

Once the world is brought up for play, we can log our LocalPlayer into the game.

play21:00

Here we spawn a PlayerController, which in

play21:02

turn spawns a PlayerState for itself and  adds that PlayerState to the GameState...

play21:07

And then we register that player with the  GameSession and cache an initial start spot.

play21:11

With the PlayerController spawned, we  can now initialize it for networking

play21:14

and associate it with our LocalPlayer...

play21:16

And then we proceed to PostLogin, where, assuming everything is set up for it,

play21:19

we can restart the player, meaning we figure out where they should start in the world,

play21:23

we figure out which Pawn class to use, and then we Spawn and initialize a Pawn.

play21:27

And then we have the PlayerController possess

play21:29

the Pawn, and we have a chance to set up  defaults for a player-controlled pawn.

play21:33

Finally, all we have to do is  route the BeginPlay event.

play21:36

This results in BeginPlay being called on  all Actors in the World, which registers

play21:40

tick functions and calls BeginPlay on all  components, and then finally, that's where

play21:43

our BeginPlay Blueprint event gets fired.  

play21:46

At that point, we're done loading the map, we've

play21:48

officially started the game, and we've finished  the initialization stage of our engine loop.

play21:56

We've covered a lot of ground here, so here  are just a few quick points to wrap up:

play22:01

We looked at the GameModeBase  and GameStateBase classes,

play22:04

rather than GameMode and GameState. These base  classes were added in Unreal 4.14, in order to

play22:10

factor out some of the Unreal-Tournament-flavored  functionality from the game mode.

play22:15

Whereas GameModeBase contains all the  essential game mode functionality,

play22:20

the GameMode class adds the concept of a  "match", with match state changes that occur

play22:24

after BeginPlay. This handles overall game flow,  like spectating before all players are ready,

play22:30

deciding when the game start and ends, and  transitioning to a new map for the next match.

play22:37

We also looked at the Pawn class, but the  GameFramework also defines a Character class,

play22:41

which is a specialized type of Pawn that  includes several useful features.

play22:46

A Character has a collision capsule that's  used primarily for movement sweeps,

play22:50

it has a skeletal mesh, so it's  assumed to be an animated character,

play22:53

it has a CharacterMovementComponent - which  is kind of tightly coupled to the Character

play22:58

class and does a few very useful things:

play23:01

The most important thing is that Character

play23:02

movement is replicated out of the box,  with client-side movement prediction.

play23:06

That's a very useful feature to have  if you're making a multiplayer game.

play23:09

Characters can also consume root  motion from animation playback

play23:12

and apply it to the actor, with replication.

play23:15

Character Movement also handles navigation and

play23:17

pathfinding, so you can have an  AIController possess a Character

play23:20

and it'll be able to move anywhere on the  navmesh that you tell it to, without you

play23:23

having to run your own navigation queries.

play23:25

And finally, Character Movement implements a

play23:27

full kitchen-sink range of movement options  for walking, jumping, falling, swimming,

play23:32

and flying; and there are lots of different  parameters for tuning movemenet behavior.

play23:36

You can take advantage of most of that  functionality at a lower level, at least in C++,

play23:40

but the Character class is a great starting  point. Just keep in mind that if you leave

play23:44

the default Character settings untouched, then  your game is just going to feel like an Unreal

play23:49

tutorial project through no fault of its own.  So it's a good idea to think about how you want

play23:53

your game's movement to feel, in the abstract, and  then tune the movement parameters accordingly.

play24:02

So, all of these classes that we've looked  at (with the exception of UWorld and ULevel)

play24:06

are here for you to extend as needed.

play24:09

We've seen how Unreal has this mature Game  Framework that has an established design

play24:14

for handling things like online integration,  login requests, and network replication.

play24:18

That means that you can develop multiplayer  games pretty easily out of the box,

play24:22

and the design of the engine allows you to add  custom functionality at pretty much any level.

play24:27

If you're mostly interested in making  simple, purely single-player games,

play24:31

then the complexity of the Game Framework  might feel kind of pointless to you.

play24:34

Just bear in mind that it's purely opt-in: for  example, if you don't need to do anything special

play24:39

before the map is loaded, then you probably  don't need a custom GameInstance class,

play24:43

and the default GameInstance implementation will  just do its job and stay out of your way.

play24:47

I still think it's useful to know what  these classes are designed for, though,

play24:51

because once you know what you're doing, it  doesn't cost you anything to use them as intended,

play24:55

and you'll generally end up with  a cleaner design that way.

play24:58

For example, if you have some information  about a player that you need to keep track of,

play25:02

there are a number of different  places you could put that data.

play25:05

For a multiplayer game, you need to  choose wisely, or you might find that

play25:08

the data isn't accessible where you need it  to be. If you're making a singleplayer game,

play25:12

you could pick pretty much any object, including  the GameMode, and the worst that happens

play25:16

is that you have to follow an awkward chain of  references to get to the data when you need it.

play25:21

But regardless of what kind of game you're  making, it's a good idea to think critically

play25:24

about how your data is structured, and how  different objects interact with each other.

play25:29

It'll make you a better programmer in the long run.

play25:32

It's also worth pointing out that extending these

play25:34

classes through inheritance isn't the only way  to add your own functionality to the engine.

play25:39

If you just need to run some code in response  to something that the Engine does, the simplest

play25:43

approach is just to bind a callback function  to a delegate that represents that event.

play25:48

In particular, the Engine defines a few  different sets of static delegates that

play25:51

you can bind to at any point.

play25:53

That includes CoreDelegates,

play25:54

CoreUObjectDelegates, GameViewportDelegates,  GameDelegates, and WorldDelegates.

play26:00

As of Unreal 4.22, the Engine also has a  "subsystem" feature that makes it easy to

play26:05

add modular functionality. All you have  to do is define a class that extends one

play26:09

of these subsystem types, and the  Engine will automatically create

play26:12

an instance of your subsystem that's tied to  the lifetime of the corresponding object.

play26:17

For example, a plugin might add custom  functionality to your project by having

play26:21

you use a custom GameInstance that's defined in  that plugin. That would work, but you'd be locked

play26:26

into that class: if there was a second plugin  that did the same thing, you'd be out of luck.

play26:31

Using a GameInstanceSubsystem instead of a  custom GameInstance would solve that problem,

play26:36

and that's generally a cleaner approach for  modular, self-contained functionality.

play26:42

So that's a look at how Unreal starts  up your game. I hope it's helped you

play26:45

understand how all the different pieces  of the Unreal game framework fit together,

play26:49

and I hope it's given you some decent  context for how the Engine works.

play26:53

It may feel like a lot to take in up front, but  I think it's useful to just be exposed to these

play26:57

design decisions and have them rattling around  in your head for when you need them later.

play27:02

These videos take a whole lot of work to put  together, but I like to think that the effort

play27:06

put into research, writing, editing, and  the accompanying motion graphics pays off

play27:10

in the end result. If you'd like to support  that work and see more videos like this,

play27:14

please consider tossing me a couple bucks  on Patreon. And thanks for watching!

Rate This

5.0 / 5 (0 votes)

Related Tags
Game LoopUnreal EngineGame DevelopmentInitializationGameModeActor ClassesBeginPlayMultiplayer GamesGame FrameworkSoftware Engineering