These are my notes for Tom Looman’s course Professional Game Development in C++ and Unreal Engine. These notes mainly consist of things i did not know or understand at the time, and things i did not want to forget. This is mainly for myself.
- About Unreal Engine
- Naming and Conventions
- Editor Tips
- C++
- Blueprint Tips
- Development Tips
- Math Tips
- Interfaces
- Multiplayer
- Collision
- UMG
- Materials
- Sound
- Anim Blueprints
- AI
- EQS
- Curves
- Commands and Cheats
- Savegame System
- Data Tables
- Data Assets
- Soft and Hard References
- Profiling
- Cooking/Packaging
- Performance Tips
About Unreal Engine
Coordinate System
X is Forward
Y is Right
Z is Up
Transforms
A transform holds Location, Rotation & Scale
Control Rotation
Control rotation refers to the rotation of the player controller. The controller’s rotation has priority over the pawn it is controlling. Setting the pawn rotation will do nothing if it is being controlled by a controller. You will need to set the controller rotation instead. So if your pawn is having problems rotating, it might be that the controller is overwriting it.
Components and transforms
Components that derive from USceneComponent have a transform (relative to its parent). So, remember to attach them to their correct parent.
Components that derive from UActorComponent don’t have a transform.
Naming and Conventions
Unreal Engine Prefixes
Unreal uses prefixes when naming their classes, these are the most common ones.
Prefix | Meaning |
---|---|
U | Derives from UObject |
A | Derives from Actor |
F | Structs |
E | Enums |
I | Interfaces |
Source Files
It is customary to add a prefix to all your source code files. This makes files easily distinguishable from engine files and other projects.
For example we could choose the prefix ‘RPG’ for a project and our source files could be “RPGMyCharacter.h/cpp”.
Editor Tips
Resetting variables to their default state
In the editor, there’s a little arrow next to variables, clicking reset’s the value to it’s default state.
Slomo Command
The “slomo” command will slow or accelerates simulation time. 1 is the default value. Between 0 and 1, it becomes slower. 1+ is faster.
Make warnings more prominent
It’s a good idea to make warnings easier to see and in your face. Turn on “Promote Output Log Warnings During PIE”.
Asset Searching Shortcut
Ctrl+P opens a window to search for assets. It’s really fast since as soon as you press the shortcut you can start typing.
Move around in PIE without stopping the simulation
Press F8 while playing in editor to be able to move around without stopping the simulation. Press F8 again to go back to your camera view.
Something should be working but its not?
The unreal editor is good but not perfect. Sometimes weird things happen that make no sense. Maybe a file is not appearing on a dropdown or something that you think should be there/working is not.
Try restarting the editor, recompiling, re-saving the filess, hell maybe even restart your computer.
C++
Whenever possible, forward declare
Try to not include headers in C++ class definitions. It is better to forward declare them for faster compilation times. Including headers will result in huge translation units.
Order your code
It’s a good idea to order your functions in the order they might get executed. For example, the constructor of a class should be near the top of the file. BeginPlay should also be near the top of the file. Mantaining a consistency like this will make finding functions faster and easier.
Stepping into engine code
To use the debugger with Unreal’s engine code, you need to download the editor symbols for debugging. This can be done from Epic Game’s Launcher.
Structs
When declaring structs in unreal, remember to prefix them with ‘F’ and add the required macro’s for unreal’s header tool.
Example:
|
|
Casting
Using regular C-style casting is not recommended. Unreal has it’s own way of casting and it’s done using an existing template.
|
|
Static Class
Sometimes you need a variable that does not need to point to a instance of a class, but to a class type. In order to get the class type we can call a method called StaticClass().
|
|
Asserts
“ensure” will assert but continue the game after. By default it will only halt execution once.
“ensureAlways” is another flavor of ensure, but this one will always halt execution.
“check” will halt execution when it’s evaluation is false.
“ensureMsgf” not only triggers the debugger when failing, but it also logs a message. This message appears in a red color inside the editor.
AActor::PostInitializeComponents
This function runs before BeginPlay. It’s a good place to bind to component events, like: “OnComponentHit”.
Const UFUNCTION
Setting a UFUNCTION to const will make it appear as a green node with no execution pin inside blueprints.
Performance
Don’t call “GetAllActorsOfClass”. It iterates through every actor on the world to find them.
Iterating through all actors in a world
To loop over all the classes in the world, we can use the class “TActorIterator”.
It’s what “GetAllActorsOfClass” uses behind the curtain.
|
|
Destroy Actor in X Seconds
The following function will set an actor to be destroyed in X seconds
|
|
Enable Ragdoll
To enable ragdoll behaviour in C++, call:
|
|
Creating a reusable function library
To create a reusable function library, create a class that inherits from “UBlueprintFunctionLibrary”. Make all the functions static, and expose them to Blueprints. An example of this is “UGameplayStatics”.
Static member functions
Static member functions are accessed by the class’s name as namespace.
|
|
Timers with parameters
You can create a timer that executes a function with parameters.
|
|
This will run FunctionName with parameters first Parameter in Time seconds
Unposses a Pawn/Character
|
|
Creating CVars (console variables)
To create a Cvar, go the .cpp file and at the top of the file declare the CVar.
|
|
Instantiating new objects
The function NewObject
Creating blueprints from UObject derived classes
Whenever you need to create a blueprint from a UObject derived class, you need to mark the class as Blueprintable
|
|
C++ Functions marked as BlueprintNativeEvent
When marking UFUNCTIONS as BlueprintNativeEvent, the C++ implementation name must append “_Implementation(…)” to it’s definition.
|
|
Accessing UWorld from blueprint classes derived from UObject
Classes that are derived from UObject don’t have access to UWorld when used in blueprints. This severely limits what can be donde in blueprints. To fix this, override the GetWorld function.
|
|
GameplayTags in C++
We cannot create GameplayTags in C++. They must be added using the unreal editor.
But we can request and access gameplay tags from C++.
|
|
Functions not appearing in blueprints
If a function is not showing up in the blueprint function list, it’s probably missing a UFUNCTION specifier. Try adding “BlueprintCallable”.
|
|
ECollisionChannel Variables in C++
Creating a variable of type “ECollisionChannel” does not compile. You need to do it in the following way:
|
|
GameplayTags Container in C++
When we need a container of gameplaytags, it’s a good idea to use FGameplayTagContainer. It’s an array fo gameplay tags with a bunch of helper functions implemented.
|
|
Blueprint Tips
How to know what class/namespace does a node belong to
A good way to know what class/namespace a blueprint node belongs to is to mouse over it and looking at it’s target.
Variables or Functions not appearing in the editor
Sometimes variables or functions won’t appear in the blueprint editor, most likely you forgot to add a property specifier. Check your C++ code.
Get Default Values of a Class
To get a class default variable values and settings, you can use the node “GetClassDefaults”.
Blueprint Performance Tips
In project settings, you can disable tick in blueprints by default. Setting this makes using the tick function a conscious decision.
“Project Settings -> Blueprints -> Can blueprints tick by default”.
Development Tips
Use Debug Shapes
Sometimes drawing things in-game can be useful for debugging. Unreal has a bunch of easy to use functions to draw things such as boxed, spheres, lines…
|
|
Get Component By Class
Use GetComponentByClass to find a specific component. This is faster than casting to player and accessing the component from player. This also has less error of invalid pointer, casting might fail.
Teleporting Actors
When you need to teleport an actor somewhere, it’s a good idea to use the built-in function “TeleportTo”. It handles a lot of edge cases when teleporting something. Such as checking for collision, valid location and more edge cases.
|
|
Math Tips
Finding a point in space where player is looking
In order to find a point in space where the player is looking, you need to get it’s rotator transform it to a vector, add the actor’s location and scale the rotation vector.
|
|
Interfaces
Interfaces are useful to create a standardized API that different actors or object might implement. For example, you might want to create an interface that implements two different member functions, such as “SaveActor” or “LoadActor”. Making you are actors inherit this interface will make it easier to implement saving and loading of actors.
Actors can also be searched by interface. For example, you might do a search for all actors that implement the “Saving/Loading” interface.
When an Interface function is marked as BlueprintNativeEvent, the function definition must append “_Implementation” to it’s name.
|
|
When an interface function is marked as BlueprintNativeEvent or BlueprintImplementable, to call it from C++ you need to prepend “Execute_” to the function name.
|
|
Collision
Collision Settings
- Try to disable overlaps until required. “bGenerateOverlaps = false”.
- Make sure “Collision Profiles” only react to necessary channels or even better, disable collision entirely.
- Set “Collision Enabled” settings to “Query Only”, unless physics simulation is required.
- Avoid Hard References wherever possible.
Object Channels
We can create new collision channels such as WorldStatic or WorldDynamic.
Go to “ProjectSettings->ObjectChannels->New”.
Show Collisions
The command “showcollision” enables a visualization of the collision primitives in game.
Multiplayer
Multiplayer Gameplay Classes
Class | Where does it exist? |
---|---|
PlayerController | Exists on the server and on the local client. |
PlayerState | Exists on the server and on all the clients |
GameMode | Only exists on the server |
GameStateBase | Exists on the server and on all the clients. This class is usually used to share GameMode data to all the clients and server |
Pawn | Every Pawn exists on the server and on all the clients also. |
AIController | Exists only on the server |
How to know if a variable is being replicated in blueprints
If a variable is being replicated, the node will have an icon that looks like two cotton balls.
How to check if the code is running on the server
The function “HasAuthority” returns true if we are the server
|
|
Checking what type of client/server we are
There’s a function call IsNetMode that returns what type of client/server we are. It has more return types that just client or server.
|
|
Updating/Replicating Actors
Actors are updated in 2 ways:
- Property Updates (Automatically send updated variables from server to clients)
- RPC’s (Execute functions on the other machin)
Variables are synced from server to client, always!
Marking an actor to replicate in C++
In order to mark an actor for replication, set it’s variable “bReplicates” to true in it’s constructor. If you need to dynamically mark an actor as replicated you can use the “SetReplicates” function.
// In Constructor, prefer this over calling SetReplicates directly
bReplicates = true;
// To set replicate at runtime
SetReplicates(true);
Replicating Actor Components
To set an ActorComponent to replicate, call “SetIsReplicatedByDefault(true)” in it’s constructor.
|
|
How to setup variables for replication
|
|
Now, the member variable MyReplicatedVariable will be synchronized to all connected clients for every copy of this actor type.
There is no need to declare GetLifetimeReplicatedProps in the header file. This is because once we mark a variable as replicated, the unreal header tool will automatically generate the definition for us.
Conditional replication of a variable
There’s another macro you can use in “GetLifetimeReplicatedProps” function to configure replication. This is the DOREPLIFETIME_CONDITION() macro.
This is mostly used to save bandwith and cpu time. The less data sent, the better. There are a lot of different conditions so be sure to check the official documentation.
|
|
Running a function everytime a variable is replicated, OnRep
Replicated variables can also run a function everytime they change. For this we need to mark the variable with “ReplicatedUsing” property.
In Blueprints, This is called “RepNotify”
|
|
The convention is to prepend the function name with “OnRep_”
OnRep and RepNotify functions are only called automatically on clients. They are not called on the server. If you need this function to be called on the server you must call it manually.
Types of Remote Procedure Calls (RPC's)
There are three types of RPC’s
- Server RPC's
- Client RPC's
- NetMulticast
Server RPC's
Functions marked as Server RPC are functions that are designed to be called on the server.
When called from a client, it requests the server to execute a function.
When called from the server, only the server runs the function.
|
|
Client RPC's
Client RPC’s are called by the server to run on a specific client.
NetMulticast
These are called by the server to run a function on all clients.
These are mostly used for transient things such as a barrel explosion. They might not be good for replicating state.
A new player that enters the server after the multicast was executed, will not receive this explosion event.
RPC's Example Declaration and Implementation
|
|
Be aware that the implementation of the function appends “_Implementation” to the function name.
Remember to replicate as little data as possible, this includes RPC function parameters!
Creating RPC's in Blueprints
- Create a new custom event
- Click on it, and select the RPC type on the details panel
- Select Reliable/Unreliable
Reliable / Unreliable
You can add a UPROPERTY/UFUNCTION specifier to functions and variables.
Reliable - This will make the variable guaranteed to update on all clients. If a network packet is lost, it will be resent until acknowledgement is received. Be aware that if a reliable packet fails to arrive, the engine will not process new packets until this one has arrived. The player might experience a lag spike.
Unreliable - Not guaranteed, packet can get lost and it won’t be resent.
Use Unrealiable as much as you can!
Where is my code being executed?
Add a ton of logging with a variable that shows whether it is running on the client or server.
Where should my code be executed?
Changing Gamestate? Execute on server.
Cosmetic? Execute on client.
They are often mixed to predict game state and reduce latency for clients.
Checking if a PlayerController is being controlled locally
There are two functions to check if a controller is being controlled locally.
|
|
IsLocallyControlled()
This function returns true if the pawn is controlled locally. It’s useful for running code only locally.
|
|
Blueprint Print Node
Printing a string in blueprints while on a multiplayer session will prepend where that node is running on a client or server. Printing “Hello World” will show up as “Client/Server: Hello World”
PlayerState
Every player has a “player state”. It’s a class which is automatically replicated to all clients and server. This is a good place to store variables that need to persist such as NumberOfKills.
GameStateBase
This class is usually used to share GameMode data to all the clients and server.
Using several replicated variables at the same time
If you need more than one replicated variable at the same time, you can wrap these in a struct and replicate the struct.
Network Relevancy
Unreal has a concept in networking called “Relevancy”. It’s mainly used to select which clients receive what data. For example, two players in fortnite really far away from each other do not need to be updated of each other since they are not near enough for this data to be relevant.
Sadly, this was not covered in the course.
Replicating UObjects
UObjects don’t have any built-in replication code. So in order to replicate UObjects you need to override the function “IsSupportedForNetworking” and just return true from it.
Also override the function “ReplicateSubObjects” and inside it call “ReplicateSubObject”.
|
|
Gotcha's
-
Be careful when destroying actors, if the actor sent an RPC it might get destroyed before it ever sends it.
-
Be careful when modifying local variables marked as RepNotifies. If we locally modify a variable, the next time the server updates the value, our OnRep function might not run. Unreal checks for differences in value to choose wether the function runs or not.
UMG
Creating a widget in C++
To create a C++ Widget class, inherit from “UserWidget”.
Instancing a widget in C++
|
|
Removing a widget
To remove a widget, call the function:
|
|
Accesing Widget variables such as textboxes from C++
To access a UMG widget in C++, do the following:
- Create a C++ class that inherits from “UserWidget”
- Add a class member variable that represents the variable/widget
- Mark it with UPROPERTY(meta=BindWidget)
- In the editor create a widget of the same type and name
- You can now access it from C++
|
|
Tick function in widget
The tick function in widget’s is called “NativeTick”, override this function when needed.
Add widget to another widget
You can add other widget to a widget. For example you could have a HUD widget, and health could be another widget. This allows us to separate things and make blueprints more modular.
Add boxed as parent to align them
You can add Boxes and set the box as parent of other widget to make them auto aligned.
Bind
You can click on bind button to add a function that runs every frame and updates the value of something. This is not recommended since running everyfame is very expensive. Prefer using delegates to update values on change/update.
Get player’s character from UMG
use GetOwningPlayerPawn to get the player’s character
|
|
Construct/BeginPlay
Construct is the BeginPlay of UMG objects. This is a good place to bind to the needed delegates!
Don't call widget functions from outside
Don’t create functions inside widgets and call them from actors. Make the UI separate from game code to avoid mantainance nightmares. Never call widgets functions from outside.
Use PreConstruct to set values
Set things such as colors and values in the pre construct event.
Make a widget have input parameters on creation
Checking “Expose on spawn” to a variable makes that variable be an input to the CreateWidget node. This way we can set variables when a widget is being created.
World Position to Screen Position
The node “ProjectWorldToScreen” transforms a position in world coordinates to screen coordinates.
If the screen coordinate seems weirdly off, try dividing ScreenPosition/GetViewPortScale.
Buttons do not need canvas
Creating a widget blueprint that is just a button does not need a canvas panel. Setting it to “Desired On Screen” instead of “FillScreen” is the way to go for buttons.
Edit multiple things at the same time
You can select multiple things and edit their appereance at the same time.
Instantiate Widgets by adding them to another widget
We can instantiate widgets by adding them to another widget instead of the viewport.
Let’s suppose we have a vertical box inside a widget and we want to add a widget inside this vertical box.
We can call VerticalBox->AddChild and remove it with VerticalBox->ClearChildren.
Materials
Materials are just shaders that are programmed using unreal’s nodes instead of glsl/hlsl. Materials are the blueprint/classes and material instances are the instances of materials.
Such as with glsl uniform variables, you can set input and parameters to a material.
Material Instances
Material instances cannot add/edit behaviours. Only variables exposed by a material are editable.
Material Functions
You can create material functions. These functions are able to be reutilized inside other materials.
Material functions can have Input, search for the input node.
When creating a material function, be sure to check the “Expose to library” checkbox. If you don’t do this, the function won’t appear in the material function list.
Material Functions can have multiple outputs!
Builtin Functions
There are a lot of complex functions already implemented for you in the material editor. I recommend having a look at them before implementing something inside a material, there’s a chance that what you want is already implemented and the solution might be to add a single node.
Debugging/Visualizing Scalar Values
One way of visualizing scalar values is to use the node “DebugScalarValues”. It prints a scalar value onto the mesh active in the material. It’s a good way of debugging values inside a shader/material.
Using Materials in UMG
In order to use a material in UMG/User Interfaces, we need to set the Material Domain to “User Interface”
Sound
Sound Cues
Sound Cues are the programmable audio assets. As with all thing in unreal you can program the sound using it’s specific nodes. Maybe add a reverb or something else…
Anim Blueprints
The event graph run every frame! To be more specific, the “Event Blueprint Update Animation” runs every frame.
The event graph inside an animation blueprint is able to set variables which are to be used inside the animation graphs to set the correct pose.
AI
Debugging Tips
Press the apostrophe key (’) to open the in-game AI Debugger. There’s a lot of information there. Press the key while looking at an AI to show the info about it’s ai instance
Debugging Tip -> We can use the “visual logger” to record gameplay and then see what was happening at different points of the playthrough. It can record a lot of events, suchs as EQS, BT’s, Pathfinding and more. It’s also good to note that the visual logger has a lot more features than AI debugging tools.
Behaviour Trees
To get the behaviour tree from C++, call the function:
|
|
Navmesh
Navmesh: The navmesh provides data on where the AI is allowed to move.
The navmesh might create valid sections inside big meshes. This might generate bugs, such as EQS generating points inside meshes. To fix this, create “Nav Modifier Volumes” and place them to delete the invalid navmesh sections.
AIController
When creating an AIController in C++ it still needs a blueprint that inherits from it.
Blackboard Component
You can get the blackboard component in AIController with the function GetBlackboardComponent
To change a value in a blackboard component, use the function:
|
|
BTServices
To create BTServices in C++ you need to inherit from “BTService”, if you are creating them from Blueprints, inherit from “BTService_BlueprintBase”.
In order to not hardcode blackboard keys as test in C++, you can create a variable like the following and set the key on the editor.
|
|
To run a Service each frame, add the frame to the rootnode of the Behavior Tree.
BTTasks
To create a BTTask node in C++, inherit ftrom BTTaskNode. To create one in blueprints, inherit from BTTask_BlueprintBase.
Marking values with UPROPERTY specifiers, make these variables editable inside the behaviour tree when selecting the corresponding node.
Decorators
Decorators have a vew flow control capabilities inside the details window of a decorator. Let’s say we have a decorator that depends on a bool. To instantly stop execution on that subtree when this bool changes, set NotifyObserver to: “OnResultChange” and ObserverAborts to: Self.
If you click on a decorator, unreal engine will apply color to different nodes. For example, unreal will color light blue those nodes that are aborted by low priority mode. Green those that are aborted by mode Self. Unreal always colors the nodes that are affected from what we have currently selected.
EQS
Running an EQS Query from C++
To run an EQS Query from C++, call:
|
|
EQS Queries might take multiple frames to complete. Remember to bind to “GetOnQueryFinishedEvent” to handle the query results.
EQS From BT's
When running an EQS from a BT, remember to set the key for the output value. For example, if running a query that will return a location, remember to set the output location key.
EQS Scoring Factor
Setting the scoring factor to a negative number makes the test weigh more in score.
EQS Querier
The “Querier” is the class that is running the query. If we run it from a BT, the querier is the AI running that BT. Queries can also be called from other classes such as game mode.
Running a Query from a non AI actor
We can run the query from another “center”, meaning a non existant EnvQueryContext. To do this we need to create a QueryContesxt. It’s a blueprint or C++ class that inherits from EnvQueryContext_BlueprintBase or the C++ one. Then, override one of the four functions and provide the possible actors/locations from which the query can be made.
Debugging Tips
You can create a special actor to visualize EQS queries in the editor. First create a blueprint that inherits from “EQSTestingPawn”, drop it in the world. Set the EQS query you want to visualize and, move the actor around to try and “refresh” it. If it all went well you should see the differentes EQS spheres and their results.
In the AI Visual debugger (’) key, press numpad 3 to enable EQS visualization for the selected AI. Blue means it failed, green succeeded. Each sphere is a queried location/point.
EQS Gotcha's
If the navmesh is not carefully created and cleaned, EQS might generate points on invalid areas, such as inside big meshes.
Senses
AI’s can have what unreal calls “senses”, such as seeing, listening for sounds and more. In the course we mainly used the “PawnSensingComponent” but there’s a new API called “AI Perception”. This new API should be used.
In the details pane of the “PawnSensingComponent” you can edit some values for the senses. Such as peripheral vision angle, which is a cone that represents the AI vision.
Curves
Unreal has an easy way to create curves for various purposes. To create a curve open the right click menu, and under “Misc” select and create a Curve. Set a few points to create the desired curve. You can access the curve from C++ by creating a variable:
|
|
Commands and Cheats
Exec
Marking a function as Exec allows it to be called as a command in the game console.
This will work automatically as long as the function is in:
- PlayerController
- Character you are playing with
- GameMode
- CheatManager
Cheat Manager
Derive a class from UCheatManager and you can add the cheats there.
Stat Commands
There a lot of stat commands, type “stat” in the console to see a list of them.
Some useful stat commands:
- stat unit
- stat graph
- stat uobjects (Shows number of instances of objects. Useful to find memory leaks / objects that are not being destroyed).
You can also create your own stat commands.
Tom Looman has a nice article explaining how to do custom stat traces & commands.
Savegame System
Location of saved game files
Save files are stored in: “ProjectName/Saved/SaveGames”
Overview of SaveGame implemented in the course
In the course we started by creating a new class that inherited from SaveGame.
Then we created a new variable and marked it with UPROPERTY so unreal can find it later.
We created two functions inside GameMode
- WriteSaveGame();
- LoadSaveGame();
Loading SaveGame
In GameMode we called our LoadGame function inside StartPlay.
SaveGame UObject
It’s a container to store variables like “Earned Credits”, unlocked abilities, player level, etc…
Saving on C++
This will be an example of saving and loading a single variable called “Credits”
- Create SaveGame UObject Instance
- Copy Creditos from PlayerState into SaveGame
- Call UGameplayStatics::SaveGameToSlot();
- Done!
Loading on C++
- Check if SaveGame file exists
- UGameplayStatics::LoadGameToSlot();
- Copy Loaded credits of SaveGame into PlayerState
- Done!
SaveGame Functions in GameplayStatics
|
|
Saving/Loading Actor State
- Mark desired variables to save with the SaveGame UPROPERTY
Saving
- Iterate through all actors in the world.
- Find relevant actors based on Interface, Class, Tags, etc..
- Actor->Serialize(EmptyArchive), this will convert all variables marked with SaveGame into an FArchive(Binary)
- Add resulting binary data containing serialized Actor Data into a SaveGame UObject
Loading
- Get binary data from SaveGame object.
- Iterate Actors in world.
- Find matching actors by name from available SaveGame Data.
- Actor->Serialize(FilledArchive) This will convert binary data into actor’s variables.
Saving/Loading moved actors
Saving
- Iterate over actors in the world.
- Find relevant actors, maybe by if they implement an interface, the type of class, if they have some tags. This is your choice.
- Create a struct to hold the actor’s Name and Transform. If the actors were moved we need to save their new location.
- Add struct into SaveGame Object
Loading
- Load Actor data (the array with names and transforms).
- Iterate actors in the world.
- Find matching Actors by Name in our SaveGame data.
- Move each Actor to it’s saved Location. Actor->SetActorTransform(LoadedTransform).
Serialize Save Data
In order to serialize and store an actor’s variables marked as SaveGame. We need to create an array of bytes in SaveGame where the binary data will be stored.
Serializing and storing API is not particularly clear, you need to do something that looks like the following:
|
|
Load Serialized Actor Data
It’s similar to save serialized data, but we use a memory reader instead of a writer.
|
|
After loading variables be sure to run the procedures that put all the remaining state back.
Example: If the actor has a bIsOpen variable, remember to update it’s state. Set the door open.
It might be a good idea to create a Save/Load interface for actors. Call OnSave when saving, OnLoad when loading.
Saving Player Settings
Player settings should not be saved in SaveGame class, there is a separate class for this!
Data Tables
Data tables are basically a spreadsheet with data. It can be created and filled inside the editor or it can be populated with a CSV import.
They cannot be modified at runtime
The can be accesed in C++ by UDataTable*
Datatables in C++
In order to use data tables in C++, we also need to create a struct that represents a single data table row with all of its variables.
This struct must inherit from FTableRowBase.
Later, we can create a datatable in the editor and make it of the type struct we defined.
Data Assets
Data assets and data tables are a good to store separate data from code.
We can create a data asset for each monster and hold all of it’s variables there, like health and anything else.
Later, when we want to edit one of those values, it’s all contained there and we won’t need to go through code to change things.
Data Asset in C++
Create a C++ class that inherits from PrimaryDataAsset. Inheriting from PrimaryDataAsset will let us use the AssetManager with this data asset.
Soft and Hard References
Hard References
Hard references are always loaded synchronously when they are referenced by another object. This means that when trying to load a single “stone” actor we might load everything instead.
This can quickly make the game slow to load, and make the development experience also slow.
Examples of hard references:
- TSubClassOf
- Pointers to assets
- Casting to certain classes are also hard references
Soft References
Soft references are not loaded automatically, they need to be loaded manually.
- They require management of loading in code.
- They load in the background and avoid game freezes.
- They reduce load screens and memory use by loading data on the fly.
Tools to see reference status in your project
Size Map - This tool shows all the hard references of an object/actor. It will let you quickly see if that stone is in fact loading everything because of hard references.
Reference Viewer - View asset references, oftean reveal
unnecessary Cast
Soft references in C++
To turn a UTexture2D hard reference into a soft ptr, do:
|
|
In UE5+, when converting a C++ hard reference to soft. When we compile, the engine will add a new node where the hard reference was used. This new node automatically returns null if the object is not already loaded in memory. It will not load it. We still have to manually load it. Be sure to check and correct everything when changin from hard to soft.
Remember, you will need to manually load this asset in code.
Loading soft references in blueprints
Use the node “Async Load Asset”.
Soft references tips
Don’t make everything a soft reference. It will become hell to manage.
Make smart and tactical decisions of which objects should be soft pointers.
Data Assets and Data Tables are a good example.
They are just data containers, but they are also hard referencing things. What looks like a small data asset might really be a lot of Megabytes when loaded into memory.
Soft pointers in C++
|
|
Async Loading of individual assets
FStreamableManager (C++) or Blueprint nodes such as AsyncLoadAsset. These two load base content such as textures, particle systems, etc…
Asset Manager
The Asset manager is mainly used to load Data Assets. In order to load those data assets you need to provide an FPrimaryAssetId. It will use this Id to identify what to load.
The asset manager needs to be configured in the project settings. Inside the “Game->AssetManager” we need to add a new “Primary Asset Types to Scan”.
Example: Primary Asset Type: “Monsters”. Asset Base Class: “SMonsterData” (This a data asset type). Directories: Add directory where to search
Loads PrimaryDataAsset asynchronously by providing FPrimaryAssetID of an asset.
Loads a DataAsset that can contain a bunch of different content to load all at once.It’s a wrapper around FStreamableManager, it’s specific to loading/unloading DataAssets.
To find a Data Asset with the asset manager, we need to override a function in our data asset C++ implementation.
Example:
|
|
Now, instead of a pointer to a data asset, we change it to a FPrimaryId, and use this id to load it.
|
|
Accessing the asset manager from C++
|
|
Loading an asset
|
|
Getting a reference to the actual data
Once the asset has finished loading and the delegate function is triggered, we can now get a pointer to the data once again using the asset manager.
|
|
Asset Manager Bundles
Bundles are a way to select what to load inside a data asset. If we only need one section of the data asset, we can load that part by using bundles.
FPrimaryAssetId
The id is basically an FName and we can set variables to be of this type.
Profiling
Always cook before profiling. Packaging and cooking may resutl in better performance.
Unreal Insights
You can add “Bookmarks to your code and make the section of the code you marked, appear in insights by name”.
Cooking/Packaging
Add levels to be packaged
In order to package the project correctly, we need to add the specific levels we want to package. By adding these levels everything reference by them will be automatically cooked and packaged.
Go to Packaging settings and add the desired levels to: “List of maps to include in a packaged build”.
Something not working in a packaged build?
Maybe some things related to the asset manager might not be working in the packaged build. Try settings the cooking rule for that type of asset to “Always Cook”.
“Game->AssetManager->Primary Type Asset To Scan->Rules->CookRule”.
Performance Tips
Avoid Ticking of Actors and Components
Most logic can be done through events or timers with lower frequency than ticks.