In this guide I try to explain many things. It's expected you have some general sense of how Unreal and it's interface works. The focus lies on doing things optimally, in the most efficient sense, hopefully ensuring your game runs smoothly from the start of development.
Don't be daunted by the length, you can come back and look for exactly the part you need when you get to it.
Whether you're newly developing your personal indie game, are a student just trying to get more knowledge, or are onboarding to a studio using Unreal and want to brush up on some basics, I hope you get some new knowledge and insights from this guide.
Blueprint Actors are perhaps the most used class in any Unreal game. They exist in the world, have Components, and all implement some of these main Events listed below.
This is the location for any scripting you’d want to do before runtime, in editor. Things like adding components, setting variables based on settings, and doing anything that can happen before the actual game runs (before runtime).
Because this is all done before the game runs, in editor, anything you do here incurs absolutely no runtime performance cost unless the Actor is dynamically spawned. Use it whenever you can. A good thing is to check your Begin Play for things that could be moved over to the Construction Script.
A good example usage would be the creation of Dynamic Material Instances, or using lots of Line Traces.
An exclusion to all of this, is if the Actor is being spawned. On Spawn the Construction script will still run, and after that Begin play will run.
Begin Play runs whenever an actor is Spawned or Loaded in. This is on runtime and as such you need to be mindful of what you do. It’s triggered only once. Things that are not time-based (like a delay or timeline), or don’t use an External Actor reference, should be moved to the construction script instead.
Tick is the most notorious place to do anything. Avoid it if you can. You can turn off ticking for an Actor on spawn by changing the setting in the Class Defaults.
You can also change the time between each tick. This is one of the simplest ways to lower the amount of ticking, setting it to 0.0 will mean that every frame Tick will be called. If you adjust it, you probably want to keep it above 0.033, which corresponds to ticking every 33 milliseconds. That converts to about 30 times per second, which is the minimum frame rate target for pretty much all platforms.
Generally very little should need to be on Tick, and a lot can be Event based (see below). However some things like interpolation of values and movement do need to run each frame.
You can also enable and disable ticking based on Events. For example you can turn off the ticking of an Actor when the player is not close, or when the Actor is no longer active for any other reason. All other Events will run even when the Tick is disabled.
A more advanced system like a Significance Manager with Significance Components can help manage Tick rates and whether something needs to Tick at all. I might make this a separate post, as it is not a basic feature.
Events are a huge part of creating a game. Instead of doing something every frame, you can use Events and Event Dispatchers to only run code when something actually happens.
Certain Components have Event Dispatchers you can bind stuff to, a much used example is On Component Begin/End Overlap.
You can find these Events in the Component’s details, under the header ‘Events’.
You can also create your own Event Dispatchers on the Actor as a whole, in the My Blueprint Window, below the Variables. You can click the ‘+’ next to Event Dispatchers to create a new Event Dispatcher, and the idea is you call it On[SomethingHappened]. So a good example would be OnSwitchActivated.
Then by dragging them into your Blueprint you can call the Event Dispatcher, and subsequently trigger all events that have been bound to this Actor’s Event Dispatcher.
In a different Blueprint (Actors, Components, and Level Blueprints) you can bind to these Event Dispatchers from a reference of this Actor, for example on Begin Play.
There are a bunch of Nodes that are inherently costly, and should probably be avoided on Tick altogether, and used sparingly outside of that. You can use some more easily in a Construction Script if you know the Actor is placed in-editor, and not spawned at runtime.
Casts are discouraged because of multiple reasons.
The main issue is hard references. When you cast to a Class, it will create a hard reference, which means that the Class will be loaded in as well. This can become problematic if relatively unrelated classes use casting to communicate with each other. It will create a spiderweb of Hard References and cause a lot of memory bloat and increased loading times. Try to only cast/reference other Actors you know are directly related and loaded in. This also has an impact on Editor performance, since when you open a level, anything that is in that level will also load its hard references.
Timelines can be useful when you don’t want to use the Tick function. They perform an update each Tick until they are done or stopped. The issues come from the fact that they are hard to optimize, there’s no way to lower the tickrate. They also count as an extra component in the Actor they are in, and that incurs an extra fixed cost.
Line traces are inherently heavy because they need to do a lot of checking in the Physics scene. Preferably this doesn’t happen each frame, but only when needed. Tracing cost is dependent on a few factors; trace length, trace complex, and optionally how many Object Types it needs to trace against.
There is a node that only traces a single primitive component called Line Trace Component which is cheaper, but less useful because you need to find the specific components first.
Pure functions are like regular functions, except for two things:
They don’t have execution pins.
They run completely each time a result is used.
On a regular function, when the execution is run it will do all logic inside the function, and save the returned values as the outputs. This allows you to use the returned values as much as you want without the function running over and over each time. This is good.
On a pure function, it will run the whole function each time a result is used on a different node, this means the returned values are not saved. This can be terrible when using a value from a pure function many times, and especially when the function is heavy, like when it contains a Line Trace.
Components exist in two flavors; Scene and Actor Components. Both are always placed inside an Actor. Making custom components is not something fundamental, and you probably won’t need to, so I’m not going to spend a lot of time explaining them.
Scene Components are put in the Component hierarchy and have a transform (location, rotation, and scale).
Actor Components are added to an Actor, and do not exist in the hierarchy or have a transform. They only contain code.
Both have many similarities with Actors in that they also have a Begin Play, Tick, and Events. However they do not have a Construction Script. Although they can have functions that can be called from the owner Actor's Construction script.
Scene Components are mostly used as physical building blocks for Actors. Only if something needs a transform, you would want it to be a Scene Component. You want to limit the amount of attached Scene Components since each transform update will cost performance, especially if the component is something with collision.
Actor Components only contain code. These can be used to easily add reusable code and functionality to an Actor. A good example are the Movement components, that you can add to any Actor, and will then allow for movement of the Actor as a whole.
Animation Blueprints are Blueprints that contain two separate Graphs for controlling Animations. The Event Graph and the Animation Graph. All optimizations in either will benefit the CPU.
Even greater optimization can be done by updating values through calling Thread Safe functions inside the ‘Blueprint Thread Safe Update Animation’ function. Unreal’s own Animation optimization docs go into this here and here. This is too much for a fundamental knowledge guide, so don’t feel obliged to do this, as it is complicated.
Do not ever push data from outside the Anim Instance into it, through either public variables or Custom Events, this will break thread safety and cost a lot. You want to only pull data from the outside inwards, like explained below.
The event graph is the place where you want to set and calculate all values you’re going to need to blend different animations and do state machine transitions. This runs on the Game Thread, and is similar to all other regular Blueprints and their Ticks. By doing all calculations here, we can run the Animation Graph on a separate core/thread, which saves us a good bunch of performance. All regular Blueprint optimizations also still apply.
On the Blueprint Initialize you’d want to get the Casted references to the Owner Character or Actor. This is so you don’t end up getting the owner each frame, which is costly.
An easy way to implement the setting of Animation Values is by binding on events from the Owner. This way you only update values when they actually change, and don’t run any unnecessary code.
On the Blueprint Update Animation you can check if the Reference you’ve made is Valid.
You should never cast in the Update sequence of events. You only need to do calculations and settings on the Update if you’re completely sure they are constantly changing, or need some type of interpolation.
The Animation Graph is the place to only do blending and animation related transitions based on values set in the Event Graph. When we don’t do any calculations inside this Graph it can be run on a separate thread in parallel with all other animations and code, saving a lot of time.
If everything has been set up correctly, all animation nodes will show a small white Lightning icon.
If you’re using values incorrectly and are doing calculations inside the Anim Graph, the node will not show the Lightning icon.
A quick way to visualize all places where there’s slow nodes is to enable the Warn About Blueprint Usage in the Class Settings. When this is enabled, the compiler results window will show warnings for each Animation node that’s not fast.
Skeletal meshes are 3D assets that are skinned to a skeleton. They are unique in that they have a lot of deformation and movement, and are different in setup and optimization compared to Static Meshes.
The first thing to do in optimizing a Skeletal Mesh would be to add LODs. These lower the complexity of a mesh at further distances where the original detail would be wasted anyway. 3 LODs is a good starting point. You add these by going to the LOD Settings, setting the number of LODs to 3, and clicking Apply Changes.
If you have specific materials that can be completely removed at further distances, like a tongue, fingernails, or eye fluids, you can specify this in the LOD 0 Sections, and then per section define the ‘Generate Up To’ LOD level. The value is inclusive, so setting it to 1 will keep the mesh data in LOD 0 and LOD 1. Using this setting will lower the amount of moving triangles from the Skeletal Mesh on a higher LOD, where you would never see the difference, by completely removing them.
You can also change settings per LOD by enabling the Custom settings in the LOD Picker.
This will show settings per LOD, and most useful are the Reduction Settings, where you can specify what percentage of triangles should remain from LOD 0, and how many bones can influence one vertex.
A general rule for any type of mesh is to limit the amount of materials. Every separate material is an extra draw call, which impacts CPU render thread performance.
When creating modular characters or systems, think of what is going to be visible when assembled and split those up into separate Skeletal Meshes that will use one Leader Pose for the animations. You don’t want to use any ‘empty materials’, those will still render the ‘invisible’ pixels and cost both CPU and GPU performance.
Static Meshes are 3D assets that are one of the most used assets in games. They are geometry (triangles and vertices) and can have various other data associated with rendering them.
The main way of rendering less triangles at further distances that’s been used for a long time are LODs. LOD stands for Level Of Detail. Lowering the Level Of Detail at a distance allows us to render less triangles and so make it cheaper for the GPU to render a mesh.
Preferably the project has set up some specific LOD Groups, you can easily select and use. Which to use when would need to be communicated in the project specific workflow and documentation.
For more specific settings you would start with ’Number of LODs’.
There’s a few main things to consider when adding LODs.
The amount of triangles and vertices in the mesh.
The size of the mesh.
The distances at which the mesh will be seen.
Your performance targets.
Each project will have a different triangle budget, so there is no general list of sizes and approximate amount of triangles you can use. The general rule is that less is better. Smaller meshes like mugs and books should not exceed a few hundred triangles, whilst a modular architecture kit for floor, walls and ceiling should also be fairly simple. Set dressing, hero assets and larger props can have some increased detail and go upwards of thousands of triangles depending on their size and importance.
Based on the distances you see your mesh you want to decide how many LODs and how much reduction in triangles you need. If something is visible from all different distances, you want it to be very low in triangles at a high LOD. Based on the original amount of triangles it has, you can set it to have up to 3 to 8 LODs. If it has a high amount of triangles, you want more LODs. By default each next LOD halves the amount of triangles.
If you only see it from close up, it might not be useful to have more than 2 or 3 LODs. If you only see it from far away, you should just make it a low triangle mesh on LOD 0.
Since UE5 we also have Nanite. Nanite is a completely new way of rendering meshes, and replaces the need for LODs. You can use a lot more triangles. It’s still smart to keep it as low as possible, for both performance and data reasons. You can enable Nanite on a mesh from the content browser or in the Mesh Asset details.
Keep in mind that any triangles that don’t actually contribute to the final image of the mesh are not needed, and would take up storage space. You can lower the Keep Triangle Percent setting to lower the amount of triangles, this will save packaged game storage space. Changing the Trim Relative Error can help in removing details that might not add anything to the actual mesh, and will also save packaged game storage space when it’s set high enough to remove triangles.
Also keep in mind that changing these settings doesn’t change the actual size of the Mesh Asset in the editor. So importing huge meshes with too much detail and lowering these numbers will still slow down the project and loading in-editor; the original high triangle mesh will still be saved in the asset, possibly bloating the project if that version is never actually used. If your Keep Triangle Percent goes below 50 you might want to consider lowering the triangles in an external software, as to not bloat the project.
When a project is also targeted for a lower performance target platform that doesn’t support or can’t handle Nanite, you need to still have LODs and make sure the Fallback is set up correctly. By default the Fallback Target is set to Auto, which can sometimes remove too much detail from the fallback mesh. You can check this by clicking the Show button in the viewport and clicking Nanite Fallback.
Then if the mesh doesn’t look correct anymore, you can set the Fallback Target to Percent Triangles. With this you can adjust the Percentage of Triangles to keep. Make sure to not just put it to 100! Set it to the lowest possible value where the mesh still looks good. Don’t forget to Apply your Changes every time you change the value so it is actually visible in the viewport.
A general rule for any type of mesh is to limit the amount of materials. Every separate material is an extra draw call, which impacts CPU render thread performance. There are some exceptions with Nanite meshes, but in the end; more of the same material is best.
Materials define the way triangles render their pixels. Think of them as functions that return specific colors and values that are then used to decide what final color the pixel on your screen will become, also depending on lighting and reflections. It all runs on the GPU.
In general the less nodes a material uses, the cheaper it is. The amount of instructions and texture samplers is generally a decent indicator of how heavy a material is. You can see this at the bottom of the material editor window.
Some of the heavier nodes are:
Power => The powers of 2/4/8/16 are cheap, but others (with fractions) are relatively heavy.
Texture Samplers => Sampling a texture is heavy, use as few textures as possible, use channel packing and avoid triplanar projection.
Acos / Atan(2) / Asin => Expensive trigonometry that's not natively executable on hardware.
If you’re using virtual textures, it is a lot cheaper to have them all use the same UV coordinates, since they can then share a Virtual Texture Stack. Using Virtual Textures will also add a significant amount of instructions, but it can be a big improvement in Texture Memory usage.
A Shader Permutation is the compiled version of a material and its usage. We want to limit these as much as possible, since each one takes storage space, memory, and requires compilation when the game is run for the first time. This is a main cause of stutters in many DirectX 12 games, and so is very important in delivering a smooth running game.
Materials can be used on a whole array of different types of geometry. Each material creates a unique shader per geometry type, and you can see which ones are enabled in the Usage section of the Details window. By default they’re always used for regular static meshes. You want as little of these checked as possible.
If you disable the Automatically Set Usage in Editor setting, it will no longer automatically set these settings above and make sure nobody is using your material in a wrong way. When somebody then uses your material on an unsupported geometry type it will show the gray Default Material, and it will be obvious it’s not to be used there.
When you want to switch between different parts of a material, you can use a Static Switch Parameter. With this you can exclude heavy calculations and textures. There is a downside to this however. Every Material Instance that changes this value from its parent will create a new bunch of Shader Permutations, for each Usage.
A general rule would be to only use a Static Switch Parameter when the Material has become very heavy and needs a simplified base Material Instance version of it. This way you can keep similar material logic in one Material Asset without needing to recreate it completely.
This diagram shows how an incorrectly set up hierarchy can cause excessive Shader permutations.
Most of the heaviest materials are translucent ones. They need to render themselves, but also still the material behind them. Together with lighting being tricky to calculate since it’s not part of the regular shadowing pass, and doesn’t cast its own shadows.
There’s a few settings to make translucent materials a little cheaper, like changing the Blend Mode to Additive on FX materials, or disabling Apply Fogging on materials for objects that’re small and/or aren’t used in heavily foggy scenes. Additionally setting the Fully Rough to enabled can make a large difference on Translucent materials for visual effects.
The Lighting Mode depends on what type of material you are making; generally glass and reflective surfaces should use Surface TranslucencyVolume, whilst particles and rough objects should use one of the Volumetric Modes, using Directional if they have Normal detail. The Volumetric Per Vertex options can cheapen lighting even more when there’s no need for per pixel lighting.
Surface Forward Shading should never have to be used.
Textures contain data that is mostly used in materials. We use textures for Color, Roughness, Normals and possibly Height, Metallic, Cloth Fuzz, Subsurface, Opacity and many other possible material properties. We want these to be as low in resolution as possible without breaking the believability of the material you are creating. Meanwhile most of these are streamed in from disk when needed and do so very quickly.
Each loaded texture takes up space. This means that we want to either make sure it’s not being referenced and loaded when we don’t need it, and that when we do need it, that it’s as small as possible. This depends on a few factors: texture compression type, resolution, and amount of streamed mips.
Mips are a chain of lower resolution versions of your original texture. This has multiple uses, but an important benefit is that we can keep only some of the lowest resolution versions in memory, so we always have correct colors available for rendering, whilst the GPU streams in higher resolutions.
The bulk of a games data is made up by textures. With smart compression techniques like those from Oodle we can minimize the space needed on disk. By default Unreal uses Oodle for textures and Kraken compression on the whole package. These work well together and mostly won't need adjustment unless you're optimizing for a specific platform.
RDO (the lossy compression setting) will optimize the texture in such a way that it will compress better in the general package compression (Kraken/ZLib), you can set this setting in the project setting and it will decrease final package sizes, at the cost of some extra build time.
Increasing the Lambda setting will lower the quality but decrease the amount of storage needed. Likewise you can set the Lambda to 1 and get less storage usage whilst having minimal perceptible difference.
An even more obvious way to decrease storage and also memory usage for textures, is to lower the resolution. This is best done by using the LOD Bias setting in your texture asset. This will define what mip level to use as the highest detail used.
Normally it's good practice to author source textures at sizes you will need them, but preferably a step or two higher in powers of two. Scaling down is easy, scaling up is hard.
WIP
WIP