Equipping yourself with tools to translate back from Blueprint to C++ is the smart thing to do, not only for the everyday fixes as mentioned previously, but also for the triple-A setting in a large company. A typical Unreal mechanics development pipeline at a large studio might follow the following process:
- A designer has an idea for a mechanic and is given time to prototype it in Blueprint as a proof of concept.
- This mechanic is tested to see if it should be developed further.
- If it receives a green light, it will be a programmer’s job to convert the prototype into a more robust C++ system.
We’ve covered the tools for designing C++ systems to work with Blueprint effectively, but when faced with the aforementioned situation, how do you take another person’s vision and translate it into something that works efficiently yet doesn’t lose what made it good? Well, the obvious answer is to directly translate it. Node for node. That process is easy, as nodes in Blueprint are quite literally just C++ functions that you have to find in the engine source. Let’s take a closer look:
- The first step is to hover the mouse over the node. Every node has a
Target
class, and mousing over will tell you what that is.
- Next, we go to the Unreal Engine docs (https://www.unrealengine.com/en-US/bing-search?) and search for U< Target class name>::<Node name with no spaces>.
- The Unreal docs page for the function is likely to be bare, but it will give you the
#include
directory for the file header containing that function. Once a class has been included, it can be used and explored via autocompleting a dot accessor.
Important note
If the target is Actor
, then it will have an A
instead of a U
at the beginning, as shown in the example in Figure 1.7. This is one of the oddities of UE5; each time there is one, it will be mentioned in a box like this.
Figure 1.7 – Showing the process from node to Unreal C++ documentation
This process may seem tedious but after a while of searching different things, you will start to see patterns and be able to work out where functions are likely to be. Each node with an execution pin (the white arrow) then becomes one line of code with input pins forming the function arguments, and output pins are generally the return type (multiple of which would form a struct).
Tip
Reading through the following headers will be useful for mastering the translation process as they are the most frequently used: AActor.h
, UGameplayStatics.h
, and UkismetSystemLibrary.h
.
The next question is, how do you know where to leave the line between C++ and Blueprints? Theoretically, you could make everything in C++, but as we’ve already shown, that is not a good idea for teamworking reasons. The answer has already been hinted at, but generally, you want everything visual or not to do with the gameplay mechanics to be Blueprint-based. Where that line exactly sits is up to interpretation and not worth agonizing over.
Worked example
To cement this theory, let’s work through an example. In Figure 1.8, you can see an example Blueprint system designed to increment an integer when it is overlapped by a projectile; then, once it reaches 10 overlaps, it will play a sound and unhide a particle effect:
Figure 1.8 – A series of Blueprint nodes in a basic function; this piece of Blueprint has been arranged using a reroute node for readability but would normally be laid out more linearly
First, we need a C++ class for this to inherit from, and seeing as it is called BP_Target
, we can name it Target
. The Target
class will need some variables; we can tell from the event that there is some kind of collision component named TargetCollision
, which has its overlap event bound to this function. As a component, it needs to be stored in a pointer; otherwise, we would be referencing the class, not an instance. We also need an integer named TimesHit
. As discussed prior, mechanics are made by a team of programmers, designers, and artists. Linking the right particle system for user feedback is a designer’s job, so we’ll leave that as Blueprint for now, but we do need a way to fire the Blueprint side, so a BlueprintImplementableEvent
will be needed. With that in mind, the header for our new class will look something like this:
Target.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Target.generated.h"
class USphereComponent;
UCLASS(Abstract)
class EXAMPLE_API ATarget : public AActor
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly)
USphereComponent* _TargetCollision;
int _TimesHit;
public:
ATarget();
UFUNCTION()
void OnTargetCollisionBeginOverlap(AActor* OtherActor,
int32 OtherBodyIndex, bool bFromSweep, const
FHitResult& SweepResult);
UFUNCTION(BlueprintImplementableEvent)
Void TenHitVisuals();
};
Important note
Notice how the UCLASS()
call includes the Abstract
tag. This will stop designers from accidentally creating instances of your C++ classes that have not had any visuals set up.
The next step is to populate the function bodies:
Target.cpp
#include "Target.h"
#include "Components/SphereComponent.h"
#include "ExampleProjectile.h"
ATarget::ATarget()
{
_TargetCollision =CreateDefaultSubobject <USphereComponent>
(TEXT("TargetCollision"));
_TargetCollision->SetupAttachment(RootComponent);
_TargetCollision->
OnComponentBeginOverlap.AddDynamic( this,
&ATarget::OnTargetCollisionBeginOverlap);
_TimesHit = 0;
}
void ATarget::OnTargetCollisionBeginOverlap
(UPrimitiveComponent* OverlappedComponent, AActor*
OtherActor, UPrimitiveComponent* OtherComp, int32
OtherBodyIndex, bool bFromSweep, const FHitResult&
SweepResult)
{
ExampleProjectile* otherProj =
Cast<AExampleProjectile>(OtherActor);
if (otherProj != nullptr)
{
_TimesHit++;
if (_TimesHit == 10)
{
TenHitVisuals();
}
}
}
The constructor is going to be standard, creating the default sub-object for the collision component and binding the overlap function to that component’s overlap event (OnTargetCollisionBeginOverlap
). In the overlap
function, the cast node becomes a cast to a temporary “cache” variable with an if
statement to check its value against nullptr
. _TimesHit
can then post increment, and the branch converts to an if
statement which, if passed, will call the BlueprintImplementableEvent
to pass the signal to the Blueprint
child class.
Note
You are not required to build this example; it is here for reference only.
There will be plenty more examples of Blueprint to C++ conversion throughout the rest of this book, but if you would like to see some first-party examples, the template projects created by Epic can be loaded in both C++ and Blueprint versions.