Using Blueprints Effectively For Iteration

Unreal Engine provides blueprints as an effective way of quickly iterating on concepts without having to wait for lengthy C++ compilation times between changes. Blueprints also provide a way for designers to access C++ implemented functionality in a visual way, so they can participate in iterating on gameplay without knowing how to code.

Because of these strengths, Blueprints are best to iterate on ideas in their earliest stages. Compared to C++, Blueprints run into inherent maintainability problems as a result of their medium. Blueprints are more difficult to analyze, debug, and navigate compared to C++ code, because the C++ Visual Studio environment (with extensions) altogether has an incredibly powerful feature set which is not matched in Blueprints. A less experienced C++ developer without knowledge of these features and options might think Blueprints are the same if not better in those regards, and this is why this document exists.

This is the only time in the entire doc performance will be mentioned, to keep the matter simple and in Blueprint’s favor. Even if Blueprints are just as fast in runtime execution as C++, we should still have nearly all gameplay implementations start in Blueprint, then implemented in C++ after iteration. While I don’t believe Blueprints are as fast as C++ due to having to work on a separate VM layer (the time cost happens when switching in and out of VM context, and moving data in and out of the VM) let’s just say for the purpose of this document, runtime performance does not matter.

This is from a perspective of someone who has used blueprints in projects for 3 years, and C++ for about 7 years. A later section of this doc gets into it, but I also want to highlight that data-only blueprints have incredible value and should be a part of our projects, because they are the cleanest way to get asset references into C++. However, this document is about code implementation. The following sections get into the capabilities of debugging and navigation, as well as implementation time cost comparisons between C++ and Blueprints.

Analysis and Troubleshooting of BP compared to C++

Debugging — Breakpoints

  • Breakpoint conditions: C++’s breakpoints in VS support “conditions”. That is, the breakpoint will only trip if a given expression that you program into the breakpoint evaluates to true. For example, if (xObj.dataMember >= y), then use this breakpoint. Notably, these breakpoint conditions can be added and removed without recompiling, so they can be added as needed in the middle of a debugging session. This is useful for debugging code which runs extremely frequently, and only rarely exhibits a bug, so you can add a condition to your breakpoint to make it only trip when the conditions you define are met. Link to breakpoint conditions VS documentation
  • Data breakpoints: C++ in VS supports a very powerful feature called “data breakpoints”. Data breakpoints allow you to pause execution whenever data at any chosen memory address changes. This is incredibly powerful for debugging an entire category of bugs, in which data is being changed unexpectedly, and/or you’re not sure where the modification is happening from. Once a data breakpoint is hit, you can inspect the stack trace to see where the data write has originated from. This is useful to determine the cause of bugs for cases where the affected data is modified from so many different locations, to the extent it is not practical to use normal breakpoints at each line of code it is modified from, especially if those contexts are called frequently for purposes other than modifying the affected data, and most importantly, to potentially find an unknown source of data writes. Link to C++ data breakpoints VS documentation
  • Breakpoint overview: In C++, you also get a breakpoint overview where you can see, search, and toggle all of your breakpoints throughout the entire codebase from one UI location. This is effective in cases where you need different breakpoints for different situations within a debugging session, and switching between sets of breakpoints quickly is needed for fast debugging. In comparison within blueprints, there exists no such control-table overview of all breakpoints — you’d have to manually go back through all the blueprints, find the nodes the breakpoints were on, re-enable them, and disable the old ones.

Both C++ and Blueprints have “Watches’’ as a feature — in which you can monitor some data or state while execution is halted, but again, C++ in VS has much greater capabilities with its watch functionality. In VS C++, you can watch the state of many objects across the scope of multiple systems, including the contents of data containers, where each element and its data members can be inspected freely. Blueprints also have a “Watch”, but it is only valid for variables that are local to the current blueprint context. Using Watches effectively is another critical part of debugging many types of issues, so having a worse form of “Watch” in blueprints makes the typical debugging experience more challenging than it needs to be.

Preventative / Proactive Debugging — Static Analysis

The types of bugs static analysis tools can detect are things like uninitialized data access (like accessing a null reference), undefined behavior, memory leaks (if not using Unreal’s memory management for parts of the project), stack overflows, infinite loops (unintentionally stuck in or loops), numeric type overflows and underflows, array overruns, and underruns, and much more. At any scale, static analysis can offer value to code quality and stability by pre-emptively detecting errors, similar to having an experienced developer review the code and point out places where there might be issues or where there will certainly be issues. The value static analysis provides increases with the scale and complexity of the project. This post on the Unreal Engine site goes into some detail about the value of static analysis, specifically in the context of Unreal Engine game development. A notable quote about static analysis is that John Carmack, the CTO of Oculus VR, went on record saying, “The most important thing I have done as a programmer in recent years is to aggressively pursue static code analysis.” Given that blueprints cannot leverage static analysis tools, that puts it at a severe disadvantage for maintainability at scale.

Speed of navigating code (or nodes) is important when evaluating code, whether you’re analyzing it while debugging it, or remembering how a system works before you modify or extend it, or while visiting many sections of code during a refactoring session. C++ is substantially faster and more versatile to navigate than blueprints with the right tools and training. These tools are standard for UE4 developers, such as Visual Assist or Resharper C++. They are so unanimously used across UE4 developers, they both have UE4-specific features and settings within them, although their best features are universal to C++. The faster code can be navigated, the faster it is to analyze and make progress on. Both C++ and blueprints have a few common navigation use cases covered like “Find all references”, but Visual Studio and third-party tools provide even greater and more flexible options than blueprints. Here’s an example use case to demonstrate the power of a couple navigation options in C++ which I use constantly while working in C++ and C#, and miss whenever I work in Blueprints. There are many more, but

C++ Navigation — Symbol Search

C++ Navigation — Peek Definition

Summary of C++ Navigation

C++ is substantially faster to create than using Blueprints. Here are some tangible examples.

Defining Functions / Methods

TArray<UPlayerCharacter*> & characters as a parameter is a reference to an array of pointers to UPlayerCharacter type.

Implementing calculations

player.maxHP = player.vitality * 10 + player.level * 5;

The equivalent in blueprints is:

1 player node.

3 “get” nodes from the player node, using three pins to get maxHP, vitality, and level variables as nodes.

2 multiply nodes, each with two input pins.

1 addition node with two inputs (each coming from the multiply nodes).

1 set node to write set the result of the addition node to player.maxHP, which has an input pin using the maxHP variable as its target, and another input pin for the value the variable should have.

8 nodes (1 player node, 2 multiply nodes, 1 add node, 1 set node, 1 get maxHP node, 1 get vitality node, 1 get level node) have to be added, and (3 get pins + 4 multiply input pins + 2 addition input pins + 1 set value pin + 1 set “target” pin) = 11 pin connections have to be made in order to do the same as that one simple line of code. The fastest part of that process would be quickly adding the multiply and addition nodes by pressing the “m” and “a” keys as shortcuts. Additionally, in blueprints, you have to take some care to line up the nodes in a way that physically makes sense to be readable. Measuring the amount of time it takes to write the line of C++ code, it took me about 7 seconds. That timing is not close to possible in blueprints. It’s certainly possible there are more efficient ways of implementing that calculation in blueprints, but the time it would take to determine the most node-and-pin minimalistic approach already makes it cost more than the C++ implementation, and the sheer amount of clicks and dragging (as well as going through the list repeatedly for which variable to get from a node) would guarantee it would always take more than the handful of seconds the C++ version takes.

Blueprints and C++ each have very different strengths and weaknesses, to the point that they shouldn’t be seen as interchangeable tools. For clarity up front, I think all of our Unreal projects should use Blueprints, but exclusively just to their strengths and no more past that. Blueprint’s key strengths are that data-only blueprints provide the best way to get asset references and other data definitions into C++ in a maintainable way, and blueprints are fast to prototype. Just in terms of early iteration times, Blueprints are faster in development than C++ because the recompile time is very fast, unlike C++’s very slow compilation. On the other hand, C++ is much easier to debug due to a rich set of debug and navigation tools and options. A C++ codebase is also easier for an experienced developer to learn and become accustomed to as a result of the navigational options and faster to create new code within due to not having to mess with a bunch of nodes and pins to write simple expressions. Further, static analysis tools enhance the process to make C++ code more stable and then maintain that stability at any scale, which objectively improves the quality of the product. However, this is not to say nothing should use Blueprints outside of prototyping. Data-only Blueprints are still the best way to get asset references from the editor into something you can access in C++. Extremely simple event-driven logic, like that in most UI implementations, is also fine to do and leave in blueprints. This is also great because UI is most iterated on by artists and designers — not developers — making blueprint a great choice for them to use.

In terms of gameplay implementation and not data-only use cases, Blueprints are best used for early iteration and prototyping. For example, when experimenting with how a feature looks and behaves. When that feature gets the sign-off, it should be converted to C++ immediately so it can benefit from all the aforementioned benefits because it will no longer benefit from the rapid iteration speed of blueprints. It is true that sometimes a feature will get revisited for iteration much later after it was signed off on — in which case, just iterate again in blueprints, then move it back to C++ when it’s determined to be the required implementation. Again, only features that are very simple and not that important to gameplay should be left in Blueprint. Everything that is important (even if simple) and/or even slightly complicated should be moved to C++.

Here’s a great video that gets into the strengths and weaknesses of blueprints. The presenter, Zak Parrish, is a senior of Developer Relations and support at Epic Games who works with development studios to improve their use of the engine. With his experience from assisting studios and working with the team at Epic Games, he has the authority to talk about what is and isn’t a good use for Blueprints. I agree entirely with what he states, and many of my opinions are the result of applying his advice to projects.

Originally published at https://megacatstudios.com on July 7, 2022.

--

--

Mega Cat Studios is a creative first company based out of Pittsburgh, PA. We love creating games. From retro cartridges to PC & VR, come play with us.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Mega Cat Studios

Mega Cat Studios is a creative first company based out of Pittsburgh, PA. We love creating games. From retro cartridges to PC & VR, come play with us.