(Translation) Introduction to C ++ Development in UE4 Part 2

Original author: Epic Games
  • Transfer
  • Tutorial
Part 1. Introduction. Create a class and add properties. C ++ class extension with Blueprint.
Part 2. Classes of gameplay. Structures Reflection in Unreal. Object / Actor iterators. Memory manager and garbage collector.
Part 3. Prefixes in class names. Integer types. Types of containers. Container Iterators. For-each loop, hash functions.
Part 4. Unreal Engine 4 for Unity developers.
Part 5. ...

image

From the Author: The beginning of the summer turned out to be hot on projects, so the translation was postponed for a long time, then it will be faster.

This article is a continuation of the translation of part of the UE4 documentation. You can find the original article by clicking on this link .

It's nice to see you still with us. In the following sections, we will study the hierarchy of gameplay classes. We will start with the base blocks and talk about how they relate to each other. We will also learn how UE uses inheritance and composition to create custom gameplay features.

Gameplay Classes: Object, Actor, and Component


Most often, four main gameplay classes are expanded. These are UObject, AActor, UActorComponent and UStruct. Next, each of them will be described in detail. You can create types that do not extend any of these classes, but they will not allow you to use most of the engine's built-in features. As a rule, classes that are created outside the UObject hierarchy (are not inheritors of any depth from UObject ) exist for the following purposes: integration of third-party libraries, wrappers of operating system features, etc.

Unreal Objects (UObject)


The base unit in the UE is the UObject class . This class, together with the UClass class , provides you with the most important basic features of the engine:
  • Reflection of properties and methods
  • Property Serialization
  • Garbage collector
  • Search UObject by name
  • Setting Values ​​for Properties
  • Network mode support for properties and methods


Each class inherited from UObject contains a UClass singleton created for it. This class contains all the metadata about the class instance. The capabilities of UObject and UClass are (jointly) at the core of all that the gameplay object does during the life cycle. It’s best to think of the difference between UClass and UObject as that UClass describes exactly what the UObject instance looks like , what properties are available for serialization, networking, etc. Basically, the development of gameplay is not related to inheritance directly from UObject , but to inherit from the AActor andUActorComponents . You do not need to know the details of how UClass / UObject works . But knowledge of their existence will be useful.

AActor


The AActor class represents an object that is part of the gameplay. An instance of this class is either placed by the designer at the level or created at runtime (using gameplay systems). All objects hosted on a level are descendants of this class. For example, AStaticMeshActor, ACameraActor, and APointLight. AActor inherits from UObject, that is, it uses the standard functions that were listed in the previous section. AActorcan be destroyed using game code (C ++ or Blueprint) or the standard garbage collection mechanism (when unloading a level from memory). It provides the high-level behavior of our game objects, and is also the basic type that provides replication for network mode. During replication (in multi-user mode), AActor also gives access to information about any of its UActorComponent, which is required to support the operation of network mode.

AActor have their own behaviors (specializes in inheritance), but in addition, they are containers for the UActorComponents hierarchy (specializes in composition). This can be done using a member of RootComponent.our AActor. It contains one UActorComponent, which, in turn, can contain many others. Before placing an AActor on a level, it must contain at least a USceneComponent that stores the position, rotation, and scale of the AActor.

AActor provides access to several events that are raised throughout the entire AActor life cycle . Below is a short list of some of them:

BeginplayCalled once when an object enters the game.
Tick Each frame is called to execute code during that frame.
Endplay Called when an object leaves the game.


For a more detailed study of the AActor class , you can follow the link .

Life cycle


In the previous section, AActor’s life cycle topic was touched on a bit . For Actor'ov located at the level, the life cycle can be imagined as follows - loading and the beginning of the existence of the Actor and its subsequent destruction (when unloading the level). Let's see what the processes of creation and destruction at runtime are. Creating an AActor is a bit more complicated than creating a regular object. This happens because AActor must be registered by various systems (run-time) - a physical engine, a manager responsible for the information received every frame, etc. Therefore, to create an object there is a method UWorld :: SpawnActor ().After the required Actor is created successfully, the BeginPlay () method is called, followed by the Tick ​​() method (in the next frame).

If Actor has existed for the time you need, you can destroy it by calling the Destroy () method . In this case, the EndPlay () method will be called , in which you can write the necessary code that executes when the object is destroyed. Another option for controlling Actor’s life is to use the LifeSpan field (lifetime). You can set this value in the constructor (or later in run-time). When the specified time elapses, the Destroy methodwill be called automatic.

You can learn more about creating Actor'ov by clicking on the link.

UActorComponent


UActorComponents have their own behavior and, as a rule, are responsible for functionality that is common to various AActors - for example, displaying meshes, particle systems, working with the camera, and physical interactions. Unlike AActors, which provide a high-level logic of their behavior in your game, UActorComponents usually perform the individual tasks required to support higher-level objects. Components may be children of other components. They can be attached to only one parent component or actor,but a component itself can have many child components. You can imagine this relationship as a tree of components. It should be remembered that child components have position, rotation, and scale relative to the parent components or Actor.

There are many options for using Actor's and components. One of the ways to imagine the relationship between Actor and components is as follows - Actor answers the question “What is this thing? (what is this thing?) ", and the components - to the question " What is this made of? (what is this thing made of?) »

  • RootComponent - An object that contains the top-level component in the AActor component tree
  • Ticking - The component that each frame is updated (ticks) is part of the Tick () method of the AActor that owns it .


Dissecting First Person Character


In previous sections there were many words without demonstrations. To illustrate the relationship between AActor and its UActorComponents, let's examine the Blueprint that is generated when a project is opened based on the First Person template . The picture below shows the component tree for the FirstPersonCharacter Actor. Here RootComponent is a CapsuleComponent. The components attached to the CapsuleComponent are ArrowComponent, Mesh, and FirstPersonCameraComponent. The most deeply nested vertex is the Mesh1P component , whose parent is FirstPersonCameraComponent.This means that the position of the mesh is considered relative to this camera.
image

Graphic this tree of components looks like shown in the picture below, where you can see all components in 3d space (except for the Mesh component)
image

This component tree is attached to one actor class. As we can see, we can create complex gameplay objects using both inheritance and composition. Inheritance should be used when modifying an existing AActor or UActorComponent, and composition if many AActor types should have similar functionality.

UStruct


To use UStruct, you do not need to inherit from any particular class, just mark the structure with the USTRUCT () macro and the building tools will do the basic work for you. Unlike UObject, the garbage collector does not track UStruct. If you dynamically create their instances, then you must independently manage their life cycle. UStructs are used so that POD types have support for UObject reflection for editing in the UE, control via Blueprint, serialization, networking, etc.

Now, after discussing the basics of hierarchies to create our gameplay classes, it's time again to choose your path. You can study the gameplay classes in more detail, explore our examples in search of additional information or continue to dive deeper into the study of C ++ features to create a game.

Diving deeper


You are sure that you want to know more. We will continue to study the work of the engine.

Unreal Reflection System


Blog post: Property system in Unreal (Reflection)

The gameplay classes use special markup, so before moving on to them, let's look at some basic things from the Unreal property system . UE4 uses its own reflection system, which provides you with various dynamic features - garbage collector, serialization, network replication and Blueprint / C ++ interoperability . These features are optional, that is, you must add the required markup for your types yourself, otherwise Unreal ignores them and does not generate the necessary data for reflection. The following is a short overview of the main markup elements:

  • UCLASS () - used to generate reflection data for the class. The class must be a descendant of UObject
  • USTRUCT () - used to generate reflection data for the structure
  • GENERATED_BODY () - UE4 replaces this with all the necessary boilerplate code that is created for the type.
  • UPROPERTY () - Enables the use of a UCLASS or USTRUCT member variable as UPROPERTY. UPROPERTY has many uses. This macro allows you to make the variable replicable, serializable and accessible from Blueprint. It is also used by the garbage collector to track the number of links to a UObject.
  • UFUNCTION () - allows the class method UCLASS or USTRUCT to be used as UFUNCTION. UFUNCTION can allow a method to be called from Blueprint and used as RPCs, etc.


UCLASS class definition example:

#include "MyObject.generated.h"
UCLASS(Blueprintable)
class UMyObject : public UObject
{
    GENERATED_BODY()
public:
    MyUObject();
    UPROPERTY(BlueprintReadOnly, EditAnywhere)
    float ExampleProperty;
    UFUNCTION(BlueprintCallable)
    void ExampleFunction();
};

This is the first time you notice the inclusion of the “MyClass.generated.h” header . Unreal will place all generated data for reflection in this file. In the declaration of your type (in the list of included files), this file should be placed last.

You may also have noticed that it is possible to add additional qualifiers to markup macros. In the code above, for demonstration, some of the most common ones have been added. They allow you to specify the specific behavior that our types possess:
  • Blueprintable - The class can be extended using Blueprint.
  • BlueprintReadOnly - The property is read-only from Blueprint, and not writable.
  • Category - Defines the section in which the property is displayed in the Details view in the editor. Used for organization.
  • BlueprintCallable - A function can be called from Blueprint.


The number of qualifiers is very large, so we will not list them here, but give links to the relevant sections of the documentation:


Object / Actor Iterators


Object iterators are a very useful tool for iterating over all instances of a particular type of UObject and its subclasses.

// Ищем ВСЕ экземпляры конкретного UObject
for (TObjectIterator It; It; ++It)
{
  UObject* CurrentObject = *It;
  UE_LOG(LogTemp, Log, TEXT("Found UObject named: %s"), *CurrentObject.GetName());
}

You can limit your search by providing a more specific type of iterator. Suppose you have a class called UMyClass inherited from UObject. You can find all instances of this class (and all that are its descendants) as follows:

for (TObjectIterator It; It; ++It)
{
  // ...
}

WARNING: Using object iterators in PIE (Play in Editor) can lead to unexpected results. While the editor is loaded, the object iterator will return all UObjects created for your instance of the game world, in addition to those used in the editor. Actor

iterators work in a similar way, like object iterators, but only work for heirs from AActor. Actor iterators do not have the problem mentioned above and return only those objects that are used in the current game world instance. When an Actor iterator is created, it needs to pass a pointer to an instance of UWorld.

Many child classes from UObject, such as APlayerController, provide this method. If you're not sure, you can check the return value of the ImplementsGetWorld method to see if a particular class supports the GetWorld method .

APlayerController* MyPC = GetMyPlayerControllerFromSomewhere();
UWorld* World = MyPC->GetWorld();
// Like object iterators, you can provide a specific class to get only objects that are
// or derive from that class
for (TActorIterator It(World); It; ++It)
{
    // ...
}

Since AActor is a descendant of UObject, you can use TObjectIterator as well to find all instances of AActors. But be careful in PIE!

Memory manager and garbage collector


Actors are usually not collected by the garbage collector. You must manually call the Destroy method after creating the Actor. Removal will not occur immediately, but only during the next phase of garbage collection.

This case is most common if you have Actor's with UObject properties .

UCLASS()
class AMyActor : public AActor
{
  GENERATED_BODY()
public:
  UPROPERTY()
  MyGCType* SafeObject;
  MyGCType* DoomedObject;
  AMyActor(const FObjectInitializer & ObjectInitializer) : Super(ObjectInitializer)
  {
    SafeObject = NewObject();
    DoomedObject = NewObject();
  }
};
void SpawnMyActor(UWorld * World, FVector Location, FRotator Rotation)
{
  World->SpawnActor(Location, Rotation);
}

When we call this function, we create an Actor in our world. Its constructor creates two objects. One labeled UPROPERTY, the other a regular pointer. As long as Actors are part of the root object, SafeObject will not be collected by the garbage collector, since it can be accessed from this root. DoomedObject, however, will have a different life cycle. Since it is not marked as UPROPERTY, the garbage collector does not know anything about the links to it and will eventually be destroyed.

When a UObject is collected by the garbage collector, all UPROPERTY links will receive nullptr values . This is done to safely check if the object is destroyed by the garbage collector or not.

if (MyActor->SafeObject != nullptr)
{
  // Use SafeObject
}

This is an important point, because, as mentioned earlier, actors for whom the Destroy () method was called are not deleted until the next phase of garbage collection. You can check if UObject is waiting to be deleted using the IsPendingKill () method . If the method returns true, the object is considered dead and should not be used.

UStructs


As stated earlier, UStructs is a lightweight version of UObject. UStructs cannot be collected by the garbage collector. If you are using a dynamic instance of UStructs, you can use smart pointers, which we will talk about later.

Links not to UObject


Typically, non-UObjects may also be able to add an object reference to prevent them from being removed by the garbage collector. To do this, the object must be an inheritor of FGCObject and override the AddReferencedObjects method .

class FMyNormalClass : public FGCObject
{
public:
  UObject * SafeObject;
  FMyNormalClass(Uobject * Object) : SafeObject(Object)
  {
  }
  void AddReferencedObjects(FReferenceCollector & Collector) override
  {
    Collector.AddReferencedObject(SafeObject);
  }
}


We use FReferenceCollector to manually add a hard link to the required UObject, which should not be collected by the garbage collector. When an object is deleted (the destructor fires), all links added by it will be deleted.

PS: I ask you to send all suggestions for correcting errors and inaccuracies in PM.

Also popular now: