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

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
one of the most fundamental concepts in game programming and one of the simplest is the idea of the game loop when your program runs you do some initialization to set things up then you run a loop for as long as the player wants to keep playing each frame you process input you update the state of the game world and you render the results to the screen when the player shuts down the game you do some cleanup and you're done but if you're writing game code in unreal engine you're not dealing with a game loop directly you don't start at the main function you start by defining a game mode subclass and overriding a function like a nit game or you're writing one-off actor and component classes and you override their begin play or tick functions to add your own logic and that's really all you have to do at a minimum the engine takes care of everything else for you which is exactly what you want when you're starting out but unreal also offers you a lot of power and flexibility as a programmer the engine is open source of course but it's also designed to be extensible in a number of different ways and even if you're a beginner before long you'll want to have a decent understanding of the engine's game framework classes like game mode game state player controller pawn and player state and one of the best ways to get more familiar with the engine is to look at its source code and see how it boots up your game if you crack open the unreal engine code base and find the main function that is the entry point of the program it may be difficult to find your way from that point to where your game code actually runs there are lots of different systems at play there's indirection for supporting different platforms there's a whole lot of conditional compilation going on to support different build configurations there are separate game and render threads and there are object oriented abstractions built on top of the core game loop functionality to make all of that complexity manageable and if you start looking at the code that initializes the engine you might find some scary looking stuff when the engine starts up before it gets into those higher level abstractions it runs thousands of lines of code to do lots and lots of little things that set up global state and initialize various systems and it's all kind of gross to look at but it's an ugly truth in any kind of software engineering that if it's long and it's complicated and it was written 20 years ago and it works you don't touch it and honestly some messy complexity is kind of inevitable at this stage it's like you're witnessing the first few moments after the big bang there's tons of stuff happening and a lot of systems overlapping each other in a very small surface area by the time you get to a knit game or begin play and game code that you've written the universe has expanded and things have settled into a more ordered form but i think it can be instructive to cut through the chaos and look at how the engine gets from the entry point of the program to actually running your game code it all begins in the launch module where you'll find different main functions defined for different platforms eventually they all find their way to this guarded main function in launch.cpp if we squint a little and maybe cut out some of this extraneous code we can see a basic game loop here the main loop for the engine is implemented in a class called engine loop we can see that the engine loop has a pre-init stage then the engine gets fully initialized and then we tick every frame until we're ready to exit let's break down what happens in these function calls pre-init is where most of the modules are loaded when you make a game project or a plugin that has c plus source code you define one or more source modules in your uproject or upload file and you can specify a loading phase to dictate when that module will be loaded the engine is split into different source modules as well some modules are more essential than others and some are only loaded on certain platforms or in certain situations so a module system helps to make sure that the dependencies between different parts of the code base are manageable and it makes sure that we can just load what we need for any given configuration when the engine loop begins its pre-init phase it loads up some low-level engine modules so that the essential systems are initialized and the essential types are defined then if your project or any enabled plugins have source modules that are in these early loading phases those are loaded next after that the bulk of higher level engine modules are loaded after that we come to the default point where project and plugin modules are loaded this is typically the point where your game c plus code is first injected into what was previously just a generic instance of unreal engine your game module comes into being at a point where all the essential engine functionality has been loaded and initialized but before any actual game state has been created so what happens when your module is loaded first the engine registers any u-object classes that are defined in that module this makes the reflection system aware of those classes and it also constructs a cdo or class default object for each class the cdo is a record of your class in its default state and it serves as a prototype for further inheritance so if you've defined a custom actor type or a custom game mode or anything declared with uclass in front of it the engine loop allocates a default instance of that class then runs its constructor passing in the cdo of the parent class as a template this is one of the reasons why the constructor shouldn't contain any gameplay related code it's really just for establishing the universal details of the class not for modifying any particular instance of that class after all your classes are registered the engine calls your module's startup module function which is matched with shutdown module giving you a chance to handle any initialization that needs to be tied to the lifetime of the module so at this point the engine loop has loaded all the required engine project and plug-in modules it's registered classes from those modules and it's initialized all the low-level systems that need to be in place that finishes the pre-init stage so we can move on to the init function the engine loops init function is comparatively straightforward if we simplify it just a little we can see that it hands things off to a class called engine prior to this point when i've said engine we've been talking about the engine with a lowercase e basically the executable that we're starting up consisting of code that we didn't write ourselves here we're introducing the engine capital e engine the engine is a software product and it contains a source module called engine and in that module is a header called engine and in that header is defined a class called engine which is implemented in both editor engine and game engine flavors during the init phase for a game engine loop checks the engine config file to figure out which game engine class should be used then it creates an instance of that class and enshrines it as the global u engine instance accessible via the global variable g-engine which is declared in engine engine.h once the engine is created it's initialized which we'll have more to say about in just a second when that's done the engine loop fires a global delegate to indicate that the engine is now initialized and then it loads any project or plug-in modules that have been configured for late loading finally the engine is started and initialization is complete so what does the engine class actually do it does a lot of things but its main responsibility lies in this set of big fat functions here including browse and load map we've looked at how the process boots up and gets all the engine systems initialized but in order to get into an actual game and start playing we have to load into a map and it's the engine class that makes that happen for us the engine is able to browse to a url which can represent either a server address to connect to as a client or the name of a map to load up locally urls can also have arguments added onto them when you set a default map in your project's default engine.ini file you're telling the engine to browse to that map automatically when it boots up of course in development builds you can also override that default map by supplying a url at the command line and you can also use the open command to browse to a different server or map during gameplay so let's look at engine initialization the engine initializes itself before the map is loaded and it does so by creating a few important objects a game instance a game viewport client and a local player you can think of the local player as representing the user who's sitting in front of a screen and you can think of the viewport client as the screen itself it's essentially a high-level interface for the rendering audio and input systems so it represents the interface between the user and the engine the game instance class was added in unreal 4.4 and it was spun off from the game engine class to handle some of the more project specific functionality that was previously handled in the engine so after the engine is initialized we have a game instance a game viewport client and a local player once that's done the game is ready to start this is where our initial call to load map occurs by the end of the load map call we'll have a world that contains all the actors that were saved into our map and we'll also have a handful of newly spawned actors that form the core of the game framework that includes a game mode a game session a game state a game network manager a player controller a player state and a pawn one of the key factors that separates these two sets of objects is lifetime at a high level there are two different lifetimes to think about there's everything that happens before a map is loaded and there's everything that happens after everything that happens before load map is tied to the lifetime of the process everything else things like game mode game state and player controller are created after the map is loaded and they only stick around for as long as you're playing in that map the engine does support what it calls seamless travel where you can transition to a different map while keeping certain actors intact but if you straight up browse to a new map or connect to a different server or back out to a main menu then all actors are destroyed the world is cleaned up and these classes are out of the picture until you load another map so let's look at what happens in load map it's a complicated function but if we pair it back to the essentials it's not that hard to follow first the engine fires a global delegate to indicate that the map is about to change then if there's already a map loaded it cleans up and destroys that world we're mostly concerned with initialization right now so we'll just wave our hand at that long story short by the time we get here there's no world what we do have though is a world context this object is created by the game instance during engine initialization and it's essentially a persistent object that keeps track of whichever world is loaded up at the moment before anything else gets loaded the game instance has a chance to pre-load any assets that it might want and by default this doesn't do anything next we need to get ourselves a world if you're working on a map in the editor the editor has a world loaded into memory along with one or more levels which contain the actors you've placed when you save your persistent level that world its level and all its actors get serialized to a map package which is written to disk as a umap file so during load map the engine finds that map package and loads it at this point the world its persistent level and all the actors in that level including the world settings have been loaded back into memory so we have a world and now we have to initialize it the engine gives the world a reference to the game instance and then it initializes a global g world variable with a reference to the world then the world is installed into the world context it has its world type initialized to game in this case and it's added to the root set which prevents it from being garbage collected init world allows the world to set up some systems like physics navigation ai and audio when we call set game mode the world asks the game instance to create a game mode actor in the world once the game mode exists the engine fully loads the map meaning any always loaded sub-levels are loaded in along with any referenced assets next we come to initialize actors for play this is what the engine refers to as bringing the world up for play here the world iterates over all actors in a few different loops the first loop registers all actor components with the world every actor component within every actor is registered which does three important things for the component first it gives it a reference to the world that it's been loaded into next it calls the component's onregister function giving it a chance to do any early initialization and if it's a primitive component when all is said and done after registration the component will have a primitive scene proxy created and added to the scene which is the render threads version of the world once components have been registered the world calls the gamemode's init game function that causes the game mode to spawn a game session actor after that we have another loop where the world goes level by level and has each level initialize all its actors that happens in two passes in the first pass the level calls the pre-initialized components function on each actor this gives actors a chance to initialize themselves fairly early at a point after their components are registered but before their components have been initialized the game mode is an actor like any other so its pre-initialized components function is called here too when that happens the game mode spawns a game state object and associates it with the world and it also spawns a game network manager before finally calling the gamemode's initgamestate function finally we finish by looping over all actors again this time calling initialize components followed by post initialize components initialize components loops over all the actors components and checks two things if the component has auto activate enabled then the component will be activated and if a component has once initialized component enabled then its initialize component function will be called post initialize components is the earliest point where the actor is in a fully formed state so it's a common place to put code that initializes the actor at the start of the game at this point our load map call is nearly done all actors have been loaded and initialized the world has been brought up for play and we now have a set of actors used to manage the overall state of the game game mode defines the rules of the game and it spawns most of the core gameplay actors it's the ultimate authority for what happens during gameplay and it only exists on the server game session and game network manager are server only as well the network manager is used to configure things like cheat detection and movement prediction and for online games the game session approves login requests and it serves as an interface to the online service like steam or psn for example the game state is created on the server and only the server has the authority to change it but it's replicated to all clients so it's where you store data that's relevant to the state of the game that you want all players to be able to know about so now the world has been fully initialized and we have the game framework actors that represent our game all we're missing now are the game framework actors that represent our player here load map iterates over all the local players present in our game instance typically there's just one for that local player it calls the spawnplay spawnplayactor function note that playactor is interchangeable with playercontroller here this function spawns a playercontroller local player as we've seen is the engine's representation of the player whereas the playercontroller is the representation of the player within the game world local player is actually a specialization of the base player class there's another player class called net connection which represents a player that's connected from a remote process in order for any player to join the game regardless of whether it's local or remote it has to go through a login process that process is handled by the game mode the gamemode's pre-login function is only called for remote connection attempts it's responsible for approving or rejecting the login request once we have the go ahead to add the player into the game either because the remote connection request was approved or because the player is local login gets called the login function spawns a player controller actor and returns it to the world of course since we're spawning an actor after the world has been brought up for play that actor gets initialized on spawn that means our playercontroller's post-initialized components function gets called and it in turn spawns a player state actor the player controller and player state are similar to the game mode and game state in that one is the server authoritative representation of the game or the player and the corresponding state object contains the data that everyone should know about the game or the player once the player controller has been spawned the world fully initializes it for networking and associates it with the player object with all that done the gamemode's post login function gets called giving the game a chance to do any setup that needs to happen as a result of this player joining by default the game mode will attempt to spawn a pawn for the new player controller on post login a pawn is just a specialized type of actor that can be possessed by a controller player controller is a specialization of the base controller class and there's another subclass called ai controller that's used for non-player characters this is a long-standing convention in unreal if you have an actor that moves around the world based on its own autonomous decision-making process whether that's a human player making decisions and translating them into raw inputs or an ai making higher level decisions about where to go and what to do then you typically have two actors the controller represents the intelligence driving the actor and the pawn is just the in-world representation of the actor so when a new player joins the game the default game mode implementation spawns a pawn for the new player controller to possess the game framework does also support spectators your player state can be configured to indicate that the player should spectate or you can configure the game mode to start all players as spectators initially in that case the game mode won't spawn a pawn and instead the player controller will spawn its own spectator pawn that allows it to fly around without interacting with the game world otherwise on post login the game mode will do what it calls restarting the player think of restarting in the context of a multiplayer shooter if a player gets killed their pawn is dead it's no longer being controlled it just hangs around as a corpse until it's destroyed but the player controller is still around and when the player is ready to respawn the game needs to spawn a new pawn for them so that's what restart player does given a player controller it'll find an actor representing where the new pawn should be spawned and then it'll figure out which pawn class to use and it'll spawn an instance of that class by default the gamemode looks through all the player start actors that have been placed in the map and picks one of them but all of this behavior can be overridden and customized in your own gamemode class in any event once a pawn has been spawned it'll be associated with the player controller and the player controller will possess it now back in load map we've got everything ready for the game to actually start all that's left to do is route the begin play event the engine tells the world the world tells the game mode the game mode tells the world settings and the world settings loops over all actors every actor has its begin play function called which in turn calls begin play on all components and the corresponding begin play events are fired in blueprints with all that done the game is fully up and running load map can finish up and we've made it into our game loop let's run through that one more time quickly just to review when we run our game in its final packaged form we're running a process the entry point of that process is a main function and the main function runs the engine loop the engine loop handles initialization then it ticks every frame and when it's done it shuts everything down right now we're mostly concerned with what happens during initialization the first point where your project or plugin code runs is going to be when your module is loaded that can happen at a number of points depending on the loading phase but typically it happens toward the end of pre-init when your module is loaded any u object classes get registered and default objects get initialized via the constructor then your module startup module function is called and this is the first place where you might hook into delegates to set up other functions to be called later the init stage is where we start setting up the engine itself in short we create an engine object we initialize it and then we start the game to initialize the engine we create a game instance and a game viewport client and then we create a local player and associate it with the game instance with those essential objects in place we can start loading up the game we figure out which map to use we browse to that map and then we let the game instance know when that's finished the rest of our startup process happens in the load map call first we find our map package then we load it this brings any actors placed into the persistent level into memory and it also gives us a world and a level object we find that world we give it a reference to the game instance we initialize some systems in the world and then we spawn a gamemode actor after that we fully load the map bringing in any always loaded sub-levels and any assets that need to be loaded with everything fully loaded we start bringing the world up for play we first register all components for every actor in every level and then we initialize the game mode which in turn spawns a game session actor and then we initialize all the actors in the world first we call pre-initialized components on every actor in every level when this happens for the game mode it spawns a game state and a game network manager and then it initializes the game state then in another loop we initialize every actor in every level this calls initialize component and potentially activate for all components that need it and then our actors become fully formed once the world is brought up for play we can log our local player into the game here we spawn a player controller which in turn spawns a player's state for itself and adds that player state to the game state and then we register that player with the game session and cash and initial start spot with the player control respond we can now initialize it for networking and associate it with our local player and then we proceed to post login where assuming everything is set up for it we can restart the player meaning we figure out where they should start in the world we figure out which pawn class to use and then we spawn and initialize a pawn and then we have the player controller possess the pawn and we have a chance to set up defaults for a player controlled pawn finally all we have to do is route the beginplay event this results in beginplay being called on all actors in the world which registers tick functions and calls begin play on all components and then finally that's where our begin play blueprint event gets fired at that point we're done loading the map we've officially started the game and we've finished the initialization stage of our engine loop we've covered a lot of ground here so here are just a few quick points to wrap up we looked at the game mode bass and game state base classes rather than game mode and game state these bass classes were added in unreal 414 in order to factor out some of the unreal tournament flavored functionality from the game mode whereas game mode base contains all the essential game mode functionality the game mode class adds the concept of a match with match state changes that occur after begin play this handles overall game flow like spectating before all players are ready deciding when the match starts and ends and transitioning to a new map for the next match we also looked at the pawn class but the game framework also defines a character class which is a specialized type of pawn that includes several useful features a character has a collision capsule that's used primarily for movement sweeps it has a skeletal mesh so it's assumed to be an animated character it has a character movement component which is kind of tightly coupled to the character class and does a few very useful things the most important thing is that character movement is replicated out of the box with client-side movement prediction that's a very useful feature to have if you're making a multiplayer game characters can also consume root motion from animation playback and apply it to the actor with replication character movement also handles navigation and pathfinding so you can have an ai controller possess a character and it'll be able to move anywhere on the navmesh that you tell it to without you having to run your own navigation queries and finally character movement implements a full kitchen sink range of movement options for walking jumping falling swimming and flying and there are lots of different parameters for tuning movement behavior you can take advantage of most of that functionality at a lower level at least in c plus plus but the character class is a great starting point just keep in mind that if you leave the default character settings untouched then your game is just going to feel like an unreal tutorial project through no fault of its own so it's a good idea to think about how you want your game's movement to feel in the abstract and then tune the movement parameters accordingly so all of these classes that we've looked at with the exception of world and level are here for you to extend as needed we've seen how unreal has this mature game framework that has an established design for handling things like online integration login requests and network replication that means that you can develop multiplayer games pretty easily out of the box and the design of the engine allows you to add custom functionality at pretty much any level if you're mostly interested in making simple purely single player games then the complexity of the game framework might feel kind of pointless to you just bear in mind that it's purely opt-in for example if you don't need to do anything special before the map is loaded then you probably don't need a custom game instance class in the default game instance implementation will just do its job and stay out of your way i still think it's useful to know what these classes are designed for though because once you know what you're doing it doesn't cost you anything to use them as intended and you'll generally end up with a cleaner design that way for example if you have some information about a player that you need to keep track of there are a number of different places you could put that data for a multiplayer game you need to choose wisely or you might find that the data isn't accessible where you need it to be if you're making a single player game you could pick pretty much any object including the game mode and the worst that happens is you have to follow an awkward chain of references to get to the data when you need it but regardless of what kind of game you're making it's a good idea to think critically about how your data is structured and how different objects interact with each other it'll make you a better programmer in the long run it's also worth pointing out that extending these classes through inheritance isn't the only way to add your own functionality to the engine if you just need to run some code in response to something that the engine does the simplest approach is just to bind a callback function to a delegate that represents that event in particular the engine defines a few different sets of static delegates that you can bind to at any point that includes core delegates core you object delegates game viewport delegates game delegates and world delegates as of unreal 422 the engine also has a subsystem feature that makes it easy to add modular functionality all you have to do is define a class that extends one of these subsystem types and the engine will automatically create an instance of your subsystem that's tied to the lifetime of the corresponding object for example a plugin might add custom functionality to your project by having you use a custom game instance class that's defined in that plugin that would work but you'd be locked into using that class if there was a second plugin that did the same thing you'd be out of luck using a game instance subsystem instead of a custom game instance would solve that problem and that's generally a cleaner approach for modular self-contained functionality so that's a look at how unreal starts up your game i hope it's helped you understand how all the different pieces of the unreal game framework fit together and i hope it's given you some decent context for how the engine works it may feel like a lot to take in up front but i think it's useful to just be exposed to these design decisions and have them rattling around in your head for when you need them later these videos take a whole lot of work to put together but i like to think that the effort put into research writing editing and the accompanying motion graphics pays off in the end result if you'd like to support that work and see more videos like this please consider tossing me a couple bucks on patreon and thanks for watching
Info
Channel: Alex Forsythe
Views: 43,249
Rating: 4.9902945 out of 5
Keywords:
Id: IaU2Hue-ApI
Channel Id: undefined
Length: 27min 22sec (1642 seconds)
Published: Fri Dec 04 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.