Unreal Engine 4 (UE4) is a powerful game engine written in C++. You are limiting what you can do in UE4 if you do not learn any C++. Knowing about core concepts to C++ such as inheritance will benefit you whether you are a programmer or designer.
My goal for this post is to provide a short overview of how inheritance works in UE4 and provide an example putting everything together.
Recommendation: Learn the basics of C++ classes before continuing.
What is Inheritance?
Inheritance is one of the most important concepts to object-oriented programming. Inheritance allows you to define a class in terms of another class, which makes it easier to create and maintain an application. To use inheritance, you start with a base (parent) class and then create derived (child) classes from the base. Inheritance works under the “is a” relationship model.
In the diagram, the base class is Animal and the derived classes are Dog, Cat, and Monkey. If you were to translate one of these relations into a statement, it would be “A monkey is an animal”. Notice the relation does not work the other way around. You cannot say, “An animal is a monkey”.
What Is Inherited From the Parent?
What the child class inherits from the parent can be confusing. Here is a table to summarize what the derived classes have access to depending on the parent’s access specifiers.
Access | Public | Protected | Private |
---|---|---|---|
Same Class | Y | Y | Y |
Derived Class | Y | Y | N |
Outside Class | Y | N | N |
Access Specifiers
There are access-specifiers that you can use in the base class to determine what variables and functions the child class inherits. There are three access-specifiers, which are public, protected, and private.
- Public: allows access in the same class, derived class, and outside classes
- Protected: allows access in the same class and derived class
- Private: allows access in the same class only
Inheritance and Blueprint
You might be wondering how does the blueprint (BP) system works with inheritance. When dealing with BP only inheritance, a variable can be private (within itself and child) or public and functions are public by default. This means if you create a BP child class from a BP parent class, all the functions are available to itself and outside classes. The variables are only available to outside classes if they are manually set to public in the parent BP class. Note that variables in BP are private by default.
Blueprint and C++ Inheritance
It gets tricky when you want to consider C++ and BP inheritance. For example, the base class is in C++ and BP is the child class. In this case, access-specifiers does not work by themselves if you want blueprints to interact with them. You will need to include some UE4 macros to make variables and functions accessible to the BP child or other BPs. Aside from the macros, accessibility works the same way as you would expect in C++.
Examples
Scenario: We have a base shape actor class and we want to derive a cube actor class from the shape actor class.
Goal: See what is inherited in C++, BP, and C++ mixed with BP
Pure C++ Parent and Child
In this example:
- The base class is “MyShapeActor”, which have some variables and functions that are private, protected, and public
- The child class (“MyCubeActor”) is in C++
- The child class modifies the inherited variables from the parent then output the result to the UE4 editor
#include "GameFramework/Actor.h" #include "MyShapeActor.generated.h" UCLASS() class VRDEMO_API AMyShapeActor : public AActor { GENERATED_BODY() private: int my_secret_number = 42; protected: UPROPERTY(EditAnywhere, BlueprintReadWrite) int length = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) int width = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) int height = 0; public: // Sets default values for this actor's properties AMyShapeActor(); // Called when the game starts or when spawned virtual void BeginPlay() override; // Called every frame virtual void Tick( float DeltaSeconds ) override; UFUNCTION(BlueprintCallable, Category = "Size") void setLength(int new_length); UFUNCTION(BlueprintCallable, Category = "Size") void setWidth(int new_width); UFUNCTION(BlueprintCallable, Category = "Size") void setHeight(int new_height); static FORCEINLINE void Print(FString text_to_print, bool print_to_log = false, FColor color = FColor::Green, float TimeToDisplay = 1.5f) { if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, TimeToDisplay, color, text_to_print); } if (print_to_log) { UE_LOG(LogTemp, Warning, TEXT("%s"), *text_to_print); } } };
#include "MyShapeActor.h" // Sets default values AMyShapeActor::AMyShapeActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; FString msg("Hello from Shape actor base class"); Print(msg, false, FColor::Red, 10.0f); } // Called when the game starts or when spawned void AMyShapeActor::BeginPlay() { Super::BeginPlay(); } // Called every frame void AMyShapeActor::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } void AMyShapeActor::setLength(int new_length) { length = new_length; } void AMyShapeActor::setWidth(int new_width) { width = new_width; } void AMyShapeActor::setHeight(int new_height) { height = new_height; }
#include "MyShapeActor.h" #include "MyCubeActor.generated.h" UCLASS() class VRDEMO_API AMyCubeActor : public AMyShapeActor { GENERATED_BODY() private: UStaticMeshComponent* my_static_mesh; protected: public: AMyCubeActor(); int getVolume(); };
#include "MyCubeActor.h" AMyCubeActor::AMyCubeActor() { PrimaryActorTick.bCanEverTick = true; static ConstructorHelpers::FObjectFinder<UStaticMesh> static_mesh(TEXT("StaticMesh'/Game/Geometry/Meshes/1M_Cube.1M_Cube'")); my_static_mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("static_mesh")); my_static_mesh->SetStaticMesh(static_mesh.Object); my_static_mesh->SetWorldScale3D(FVector(0.3f, 0.3f, 0.3f)); RootComponent = my_static_mesh; length = 11; width = 11; height = 11; getVolume(); } int AMyCubeActor::getVolume() { int volume = length * width * height; FString msg("Hi from Cube Actor Child - C++\nlength = "); msg.AppendInt(length); msg.Append(" , width = "); msg.AppendInt(width); msg.Append(", height = "); msg.AppendInt(height); msg.Append("\nVolume = "); msg.AppendInt(volume); Print(msg, false, FColor::Green, 10.0f); return volume; }
This example demonstrates:
- Protected variables & functions are inherited
- Private variables & functions are not inherited
- Public variables & functions are inherited
- Base class default constructor always get called for the child class
- Can extend child class with new functions and variables
- Inherited variables can be modified
Pure BP Parent and Child
In this example:
- the BP Base class is “MyShapeActor_BP”
- the child BP class is “MyCubeActor_BP_Child”
- the base class has one private variable and three public variables
- The base class also has three functions. By default the functions are all public.
- the child class modifies the inherited variables from the parents
This example demonstrates:
- public and private variables are inherited by child class (private variables are not accessible by outside classes)
- inherited variables can be modified
- the parent’s constructor/construction script gets automatically called by the child BP
- Printing to editor does not work at construction. At earliest, you can print at begin play
C++ Parent and BP Child
In this example:
- Base class is “MyShapeActor” in C++
- Base class have 3 protected variables, 1 protected, and 3 public functions. Each of them has an UE4 macro attached to them that makes them accessible by the child BP and other BPs
- Child class is “MyCubeActor_BP_CPP” in BP
- The child class modifies the value of the variables inherited and extends what it inherited with its own “getVolume” BP function
#include "GameFramework/Actor.h" #include "MyShapeActor.generated.h" UCLASS() class VRDEMO_API AMyShapeActor : public AActor { GENERATED_BODY() private: int my_secret_number = 42; protected: UPROPERTY(EditAnywhere, BlueprintReadWrite) int length = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) int width = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) int height = 0; public: // Sets default values for this actor's properties AMyShapeActor(); // Called when the game starts or when spawned virtual void BeginPlay() override; // Called every frame virtual void Tick( float DeltaSeconds ) override; UFUNCTION(BlueprintCallable, Category = "Size") void setLength(int new_length); UFUNCTION(BlueprintCallable, Category = "Size") void setWidth(int new_width); UFUNCTION(BlueprintCallable, Category = "Size") void setHeight(int new_height); static FORCEINLINE void Print(FString text_to_print, bool print_to_log = false, FColor color = FColor::Green, float TimeToDisplay = 1.5f) { if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, TimeToDisplay, color, text_to_print); } if (print_to_log) { UE_LOG(LogTemp, Warning, TEXT("%s"), *text_to_print); } } };
#include "MyShapeActor.h" // Sets default values AMyShapeActor::AMyShapeActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; FString msg("Hello from Shape actor base class"); Print(msg, false, FColor::Red, 10.0f); } // Called when the game starts or when spawned void AMyShapeActor::BeginPlay() { Super::BeginPlay(); } // Called every frame void AMyShapeActor::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } void AMyShapeActor::setLength(int new_length) { length = new_length; } void AMyShapeActor::setWidth(int new_width) { width = new_width; } void AMyShapeActor::setHeight(int new_height) { height = new_height; }
This example demonstrates:
- Variables and functions do not work for BP unless you add UE4 macros
- Inherited variables show up in UE4 editor, but not functions. To access inherited functions, you need to right click in an empty spot and search for the function name
- The base class default constructor gets called even though the child’s construction script does not show it
I hope this post provided you with a base foundation to how C++ inheritance works. If you found this post helpful, share it with others so they can benefit too.
Are the examples understandable? Do you think there is something else that I should add that you believe will be helpful? Are there concepts that need more clarifications?
Leave a comment or send me an email at steven@brightdevelopers.com. To stay in touch, follow me on Twitter.
Pingback: Unreal Engine 4 C++ Polymorphism Overview - bright developers