3. C++ and Unreal Engine 4

2021/01 02 20:01

C++ is used in Unreal Engine 4 to create gameplay elements or modify the engine. In this chapter, you learn how to install Visual Studio for Unreal Engine. You also learn how to create C++ classes and see how they are structured.

Installing Visual Studio

Before you can write and compile C++ code, you need to have Microsoft Visual Studio (Community Edition or higher) installed on your machine (on macOS, you need Xcode). If you are installing Visual Studio for the first time, make sure to include C++ in your installation (see Figure 3-1).

../images/496849_1_En_3_Chapter/496849_1_En_3_Fig1_HTML.jpg
Figure 3-1Install Visual Studio with Game Development with C++ enabled

After the installation, restart your PC. You can start writing C++ in Unreal Engine 4.

Adding C++ Code

You add new C++ classes from Unreal Editor. A C++ class is a user-defined data type that holds its own variables and functions, which are accessed by creating an instance of that class. These variables and functions define the behavior of the class. Class definitions do not take up any memory, but memory is allocated when an instance of the class is created. In UE4, Blueprints extend from one of the classes created in C++ and inherit all the class properties.

In this chapter you create an Actor-based class that can be placed on your level. In Unreal Engine, there are two main classes that you need to be aware of: the Actor class and the Object class. Even though usage depends on your goals, there are some things you must keep in mind when creating classes.

Any Actor-based class can be placed or spawned in the level. These classes have a visual representation and support networking. Object-based classes are generally for storing data, and their memory size is typically smaller than Actor-based classes.In our Actor-based class, you expose certain properties and functions to Blueprints, which you use to modify the Actor’s behavior. To create a new C++ class, click the file menu and select the New C++ Class… option (see Figure 3-2). Alternatively, you can right-click the Content Browser and select New C++ Class. To create a Visual Studio (*.sln) file from the uproject file, right-click the *.uproject file and select Generate Visual Studio project files.

../images/496849_1_En_3_Chapter/496849_1_En_3_Fig2_HTML.jpg
Figure 3-2.Creating new C++ from File Menu

You are prompted by a wizard, where you can select the base class. From the list of classes, select the Actor class (see Figure 3-3), and then click Next.

../images/496849_1_En_3_Chapter/496849_1_En_3_Fig3_HTML.jpg
Figure 3-3.Select Actor class

The next page prompts you to name your class, enter the location to save to, and chose whether you want to organize your class into a folder structure (see Figure 3-4).

../images/496849_1_En_3_Chapter/496849_1_En_3_Fig4_HTML.jpg
Figure 3-4.Choose name, location and scope

Generally, it’s a good and recommended practice to arrange your header in a public folder and your source files in a private folder, so select the public option, which puts the header file in the Public folder and the source file in the Private folder. For now, let’s stick with the default MyActor name and click Create Class.

Unreal Engine now adds the code to your project and starts compiling C++ code. This might take a few seconds to finish, so you can go to the project folder and see which new folders and files were created.In your root project folder, you see the following new folders.

  • Binaries: This folder contains executable files or other files created during compiling. It can be deleted if the Editor is not running and is created next time you compile the project.
  • Intermediate: This folder contains temporary game object files and Visual Studio–generated project files. It can be deleted safely.
  • Source: This folder contains your game-specific code files. Obviously, don’t delete it.

If you go inside the Source folder, you see that some extra files were created, such as the YourProjectName.Target.cs and YourProjectName.Build.cs files (the actual name of your project replaces YourProjectName). The Target file determines the build settings (how your project is built) for a specific target (such as Game, Editor, etc.), whereas the build file determines which modules should be built. Modules are containers, including a collection of C++ classes accompanied by a C# build file (with the *.build.cs extension). When you build a module, a corresponding DLL file is generated in the Binaries folder. When you ship your project, all modules are linked together in a single EXE file.

Inside the Public folder, MyActor.h is the header file. Inside the Private folder, MyActor.cpp is the source file. The Header file is where you declare your variables and functions. You implement variables and functions in the Source file.

EXAMINING THE HEADER

Let’s analyze the MyActor.h file.#pragma once#include “CoreMinimal.h”#include “GameFramework/Actor.h”#include “MyActor.generated.h”UCLASS()class BOOKTEMPLATE_API AMyActor : public AActor{     GENERATED_BODY()public:     // Sets default values for this actor’s properties     AMyActor();protected:     // Called when the game starts or when spawned     virtual void BeginPlay() override;public:     // Called every frame     virtual void Tick(float DeltaTime) override;};

#pragma once

#pragma once is called a preprocessor directive, which means that you only include this header file once. If you include MyActor.h multiple times in any other file, all subsequent inclusions are ignored.

UCLASS( ) macro

UCLASS() macro is a special macro required by Unreal Engine to make the Editor aware of the class and include it for serialization, optimization, and other Engine-related functionalities. This macro is paired with the GENERATED_BODY() macro, which includes additional functions and type definitions in the class body.Note

The UCLASS( ) macro can take parameters (a.k.a., Specifiers). You can read more about them at https://docs.unrealengine.com/en-US/Programming/UnrealArchitecture/Reference/Classes/Specifiers/index.html.

class BOOKTEMPLATE_API AMyActor : public AActor

class BOOKTEMPLATE_API AMyActor : public AActor is the beginning of your class. The _API macro is related to DLL linkage, which means it tags functions, classes, or data public to the DLL file; any other module that imports this API module can access these classes or functions directly. This is passed to the compiler from Unreal Build Tool. public AActor means that you inherited this class from Actor type. Inheriting is a feature in which you create a new class (also known as a derived class) from an existing class (also known as a base class). The inherited class derives all the features of its base class (also known as a parent class) and can have its own functionality.In this line, you might have noticed prefixes on class names. Instead of MyActor, it is AMyActor. This is because the Unreal reflection system requires classes to be prefixed by a certain letter. The reflection system is Unreal Engine’s foundational technology. It can examine itself at runtime. The following is a list of the prefixes and what each means.

  • A for Actor type (e.g., AActor, AController, AGameMode)
  • U for Unreal Object (e.g., UObject, UActorComponent, USceneComponent)
  • T for Template (e.g., TWeakPtr, TArray, TMap)
  • S for Slate (e.g., SWidget, SCompundWidget, SCurveEditor)
  • I for Interface (e.g., IAssetRegistry, ILevelViewport, IAsyncTask)
  • E for Enum (e.g., EAnchorWidget, EAcceptConnection)
  • G for Global (e.g., GEditor, GWorld)
  • F for Float (e.g., FVector, FGameplayTag)

public:, protected:, and private:

public:, protected:, and private: are called access specifiers (also known as access modifiers). They define how variables, functions, and so forth are accessed outside of this class.

  • public: Any outside class can access members
  • protected: Any inheriting class can access members
  • private: No other class can access members

AMyActor( )

AMyActor() is the constructor, which is a special function that is automatically called when an object is created. It has the same name as the class and never returns any type. It is where you initialize all the default values for any type defined in the header files. For example, if you create a variable with type int32 (like int32 MyVariable;) inside the constructor, you can assign any default value, such as MyVariable = 1.

virtual void BeginPlay( ) override

The override keyword means this function was already declared and defined in the Actor class, and you are overriding it to have your own custom functionality. So, you declare the BeginPlay function that is from the Actor class. The same idea applies to the Tick method.

EXAMINING THE SOURCE FILE

The header file only declares the functions and contains no implementation (the code carries out no actions). Since all implementation is done in the source file (*.cpp), let’s look at the source file.#include “MyActor.h”AMyActor::AMyActor(){     PrimaryActorTick.bCanEverTick = true;}void AMyActor::BeginPlay(){     Super::BeginPlay();}void AMyActor::Tick(float DeltaTime){     Super::Tick(DeltaTime);}

This source file contains only a very basic implementation. Something important to remember here are the Super calls (Super::BeginPlay(); and Super::Tick();), which mean that even when you override these functions, they still call the base implementation defined in the parent class. It is extremely important to include Super calls if you are overriding native engine implementations.

Exposing Variables and Functions to Blueprints

From the C++ class, you can expose the functions or variables that you need to Blueprints so designers can modify them accordingly. You modify the newly added actor to have variables and functions exposed to Blueprints.

MODIFYING THE HEADER

First, let’s add a few variables. Add the following code under the GENERATED_BODY() macro.private:     /* Our root component for this actor. We can assign a Mesh to this component using Mesh variable. */     UPROPERTY(VisibleAnywhere)     UStaticMeshComponent* MeshComponent;     /* Determines if this item can be collected. */     UPROPERTY(EditAnywhere)     bool bCanBeCollected;     /* Just an example to show toggleable option using metadata specifier. */     UPROPERTY(EditAnywhere, meta = (EditCondition = “bCanBeCollected”))     int32 ToggleableOption;

  • UCLASS macros are for classes.
  • UPROPERTY macros are for variables.
  • UFUNCTION macros are for functions.

UPROPERTY is a special engine macro. Inside, you specify how to expose your variable.Here are some of the common specifiers for UPROPERTY.

  • EditAnywhere: This property can be edited in default Blueprint and the instances placed in the world.
  • EditDefaultsOnly: This property can be edited in default Blueprint only. When you place the instances of an actor in the world, you cannot edit this property individually by instance.
  • EditInstanceOnly: This property can only be changed for instances placed in the level. This property is not available in default Blueprint.
  • VisibleAnywhere: This property has the same visibility as EditAnywhere, but the property cannot be edited. It is read-only.
  • VisibleDefaultsOnly: This property has the same visibility as EditDefaultsOnly, but the property cannot be edited. It is read-only.
  • VisibleInstanceOnly: This property has the same visibility as EditInstanceOnly, but the property cannot be edited. It is read-only.

If necessary, property editing can be enabled or disabled based on boolean values. This is achieved using a metadata specifier called EditCondition. An example is provided in the preceding code.Underneath AMyActor();, add a function that is exposed to the Blueprint./* Just a sample Blueprint exposed function. This comment appears as a tooltip in Blueprint Graph. */UFUNCTION(BlueprintCallable, Category = “My Actor”)void CollectMe(bool bDestroy = true);

MODIFYING THE SOURCE FILE

Now let’s modify the source file. First, you need to assign a Mesh Component variable, which is a Static Mesh Component type used to create an instance of Static Mesh. You do this because you need a valid root component to move this actor in the world. To do that, you need to construct the Static Mesh Component object inside the constructor and assign it there. Let’s look at the following example code.MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT(“MeshComponent”));RootComponent = MeshComponent;

In the code, you see a special Engine function called CreateDefaultSubobject. This function allows you to create an object of the given type, which is visible in the Editor. This function can only be called inside constructors; calling it at runtime crashes the Editor.

Then create a definition for the CollectMe function inside the source file.Note

Function names can be anything, but generally, it is recommended that they are verbs that describe the function’s usage or are based on the return value.For now, you log information based on the input parameter and the bCanBeCollected variable. Define the function as follows.void AMyActor::CollectMe(bool bDestroy /*= true*/){     // Check if this actor can be collected…     if (bCanBeCollected)     {         // …Now check if the actor must be destroyed…         if (bDestroy)         {               // …Actor has to be destroyed so log that information and destroy.               UE_LOG(LogTemp, Log, TEXT(“Actor collected and destroyed.”));               Destroy();          }          else          {               // …Dont destroy the actor. Just log the information.               UE_LOG(LogTemp, Warning, TEXT(“Actor collected but not destroyed.”));          }     }     else     {          // …Actor cannot be collected thus cannot be destroyed.          UE_LOG(LogTemp, Error, TEXT(“Actor not collected.”));     }}

THE FINAL CODE

The entire header should look like the following code. Note that the BeginPlay and Tick functions have been removed.#pragma once#include “CoreMinimal.h”#include “GameFramework/Actor.h”#include “MyActor.generated.h”UCLASS()class BOOKTEMPLATE_API AMyActor : public AActor{     GENERATED_BODY()private:     /* Our root component for this actor. We can assign a Mesh to this component using Mesh variable. */     UPROPERTY(VisibleAnywhere)     UStaticMeshComponent* MeshComponent;     /* Determines if this item can be collected. */     UPROPERTY(EditAnywhere)     bool bCanBeCollected;     /* Just an example to show toggleable option using metadata specifier. Uncheck Can Be Collected boolean and this option  disable (grey out). */     UPROPERTY(EditAnywhere, meta = (EditCondition = “bCanBeCollected”))     int32 ToggleableOption;public:     AMyActor();     /* Collect this actor. Blueprint exposed example function. This also acts as a tooltip in Blueprint Graph. */     UFUNCTION(BlueprintCallable, Category = “My Actor”)     void CollectMe(bool bDestroy = true);};And the source code should look like this.#include “MyActor.h”#include “Components/StaticMeshComponent.h”AMyActor::AMyActor(){     MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT(“MeshComponent”));     RootComponent = MeshComponent;     bCanBeCollected = true;     ToggleableOption = 0;     // 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;}void AMyActor::CollectMe(bool bDestroy /*= true*/){     // Check if this actor can be collected …     if (bCanBeCollected)     {          // …Now check if the actor must be destroyed…          if (bDestroy)          {               // …Actor has to be destroyed so log that information and destroy.               UE_LOG(LogTemp, Log, TEXT(“Actor collected and destroyed.”));               Destroy();          }          else          {               // …Dont destroy the actor. Just log a warning.               UE_LOG(LogTemp, Warning, TEXT(“Actor collected but not destroyed.”));          }     }     else     {          // …Actor cannot be collected thus cannot be destroyed. Log as an error.          UE_LOG(LogTemp, Error, TEXT(“Actor not collected.”));     }}

USING THE CLASS

In Visual Studio, press F5 to compile and launch the project. Once the Editor is up and running, right-click the Content Browser and select Blueprint Class. In the Pick Parent Class window, expand all the classes and search for My Actor. You should then pick our custom Actor class as a base for Blueprint (see Figure 3-5).

../images/496849_1_En_3_Chapter/496849_1_En_3_Fig5_HTML.jpg
Figure 3-5.Select our previously created C++ class as parent

Next, open the Blueprint Actor. You can assign your custom mesh and adjust the properties you chose to expose to Blueprint (see Figure 3-6). Hovering your mouse over the properties shows the comment you added in C++ as a tooltip.

../images/496849_1_En_3_Chapter/496849_1_En_3_Fig6_HTML.jpg
Figure 3-6.Adjust properties exposed from C++

Calling C++ Functions in Blueprints

My Actor Blueprint is ready in the Editor. You also made a Blueprint Callable function inside the actor called Collect Me. In this section, we use relatively simple logic to quickly call this function in the Level Blueprint.First, drag and drop the newly created My Actor Blueprint onto the level. After that, make sure My Actor Blueprint is selected in the Level Editor and open Level Blueprint. Inside Level Blueprint, right-click the graph and create a reference to the selected My Actor Blueprint. In Figure 3-7, I placed My Actor Blueprint on the level, selected it, and created a reference in Level Blueprint.

../images/496849_1_En_3_Chapter/496849_1_En_3_Fig7_HTML.jpg
Figure 3-7.Referencing My Actor Blueprint

From the reference, drag a pin and select the Collect Me function (see Figure 3-8).

../images/496849_1_En_3_Chapter/496849_1_En_3_Fig8_HTML.jpg
Figure 3-8.Calling Collect Me function

Now you can call the Collect Me function any time you want. As an example, let’s call it in Begin Play after a two-second delay. The final graph looks like Figure 3-9.

../images/496849_1_En_3_Chapter/496849_1_En_3_Fig9_HTML.jpg
Figure 3-9.Final Graph

Even though you only used a boolean as an input here, you can use other types, like floats, integers, units, and even UObjects or AActors. For example, if you want to use int32, change the boolean to int32 in both the header and source files. Void CollectMe(int32 MyIntVariable);. After that, you can use MyIntVariable in the CollectMe function defined in the source file.Click the Play button. After 2 seconds, the My Actor Blueprint prints the message (see Figure 3-10) to log as defined in C++ and destroys itself.

../images/496849_1_En_3_Chapter/496849_1_En_3_Fig10_HTML.jpg
Figure 3-10.Logged information displayed

--转载请注明: http://blog.coolcoding.cn/?p=3377