Pitfalls and Traps of Blueprints

After years with Unreal Engine, I’ve had my fair share of head-scratching moments and difficulties with Blueprints. I’m writing this article to share my experience and maybe save you a headache or two. It’s for those of you who’ve got your feet wet but want to avoid stepping on some sneaky Blueprint landmines.

Blueprints are inherently slower than C++. They operate through a virtual machine, which by default, leads to a dip in performance. Consider this scenario: you’re developing a game featuring complex physics simulations. The calculations involved would run far more efficiently in C++ due to its direct interaction with the hardware, as opposed to Blueprints. Always be extra diligent when handling Blueprints; their slower nature demands it.

Readability is another concern worth highlighting. Picture this: you have a blueprint with hundreds of nodes, each connected with a string of lines, resembling a child’s wild scribble. Not only does it put a strain on your eyes, but it also complicates debugging and node management. Thus, investing time in maintaining the readability of your blueprints pays dividends in the long run.


Example of bad node readability

Example of good node readability

Clean code usually has comments, straight execution flow, little to no overlapping connections and mechanics encapsulated in functions or macros.  In some circumstances you also might use sequences to make the execution order clearer.

Next up is references. Here’s a pitfall that’s easy to fall into: making everything a hard reference. It’s like having a family reunion every time you just want to invite Uncle Bob. Before you know it, you’ve got a boatload of assets loaded, and your game’s performance and memory goes down the drain. As an antidote, use soft references to minimize asset loading initially, then load assets only when necessary.

Example of a character that directly references assets, with total size of 1.9GB

By right clicking on an asset in the content browser->Size Map, you can see all of the primary and secondary assets will be loaded when the asset in question is loaded. Sometimes it is necessary to load a lot of assets, but most of the time you can decrease this number by a lot! In the case above, with some optimization, you can decrease the memory footprint by 60-70%.

Casting, when misused, can transform into a performance guzzler. Picture a game where you have multiple character classes inheriting from a base ‘Character’ class. If you attempt to cast the base class to a specific class frequently, it can degrade your performance, given its CPU-intensive nature. The optimal strategy? Use casting sparingly, and store the reference as a variable after casting to avoid repetitive operations.

 

This event constantly ticks and casting is expensive. Better way would be to cast once on begin play and save the reference to FGearVehicle.

Loops, while powerful, can also pose a performance threat in Blueprints. For instance, imagine a game mechanic where you iterate over a thousand objects every frame, changing their positions. With Blueprints, this loop would quickly become a performance bottleneck. If your game heavily depends on such loops, consider transferring this logic to C++ or spreading the loop over multiple frames to alleviate the performance impact.

Tick events are another point of caution. A tick event executing a complex operation at a rate of 60 times per second (for a game running at 60 FPS) can drag your game’s performance down a lot. Combine that with loops or casting, and you’ve got yourself a recipe for a performance disaster. So, use tick events as little as you can. Two ways to mitigate this would be to decrease the tick rate(option in class defaults) or use timers.

In terms of modularity, Blueprints can lull you into a trap of dumping all your functions and variables into one monolithic Blueprint. For example, a character blueprint holding movement, camera controls, and ability logic. This approach hampers readability and increases complexity. Instead, compartmentalize these functionalities into distinct components, promoting manageable and clean BP code.

In this character blueprint, there are tons of variables, interfaces and functions. It looks messy and is hard to work with.

Lastly, let’s talk about duplicate nodes or code. If you have a ‘Health Regen’ function used in several Blueprints, it’s tempting to copy-paste it everywhere. However, this creates redundancy and increases the likelihood of errors. The smart move here is to encapsulate this function in a Blueprint function library or Blueprint macro library. This enables you to centralize your code, and for inherited functionalities, consider using inheritance.

 

Function Library does not have events, only functions. These functions are accessible from every other blueprint in your project.

In conclusion, Blueprints, while a powerful tool in Unreal Engine, come with their own set of challenges. Yet, by understanding these pitfalls and equipping yourself with these practical solutions, you can mitigate A LOT of the negatives that come working with Blueprints.