Basics of C ++ gameplay for Unreal Engine

Original author: Tom Looman
  • Transfer
image

The basis of the gameplay for Unreal Engine 4 provides the developer with a powerful set of classes for creating the game. Your project can be a shooter, a farm simulator, deep RPG - it doesn’t matter, the foundation is very versatile, does some of the hard work for you and sets some standards. It is pretty much integrated into the engine, so I recommend that you stick to these classes, and not try to invent your own game base, as is often the case with engines like Unity3D. Understanding this foundation is very important for successful and effective project work.

Who is this article for?


For anyone interested in creating games in UE4, specifically in C ++, and who want to learn more about the basis of Unreal gameplay. This post discusses the base classes that you will use as the basis of gameplay, and explains their application, the process of creating their instances with the engine, and how to access these classes from other parts of the game code. Most of the information is also true for bluints.

If you want to learn the basics of Unreal Engine 4, then check out my previous tutorial . I also have a separate guide on virtual reality for beginners . It is useful for those who study the specifics of VR in Unreal Engine 4.

When creating games in Unreal Engine 4, you will find many pre-made boilerplate blanks. There are several classes that you will often use when creating games in C ++ or in blueprints. We will look at each of these classes, their nice features, and learn how to refer to them from other parts of the code. Most of the information in this guide applies to blueprints, however, I use fragments of C ++ code and therefore some functions will not be available in blunts and will be useful only to C ++ users.

Actor


Probably the most commonly used class in games. Actor is the basis for any object at the level, including for players controlled by AI enemies, doors, walls and gameplay objects. Actors are created using ActorComponents (see the next section), such as StaticMeshComponent, CharacterMovementComponent, ParticleComponent and many others. Even classes such as GameMode (see below) are actors (although GameMode does not have a "real" position in the world). Let's discuss a couple of aspects you need to know about actors.

Actor is a class that can be replicated over the network (for multi-user mode). This is easily done by calling the SetReplicates (true) constructor. To create effective network programming of actors, it is necessary to take into account many aspects that I cannot discuss in this article.

Actors support the concept of taking damage. Damage can be done directly to the actor using MyActor-> TakeDamage (...) or through UGameplayStatics :: ApplyDamage (...). It is worth considering that there are variations: PointDamage (for example, for weapons, hit from which is calculated by ray tracing (hitscan)) and RadialDamage (for example, for explosions). The Unreal Engine official site has a great Damage in UE4 introductory article .

To create a new instance of an actor in the code, simply use GetWorld () -> SpawnActor(...); where T is the return class, for example, AActor for one of your own classes - AGadgetActor, AGameplayProp, etc.

Here is an example of code in which an actor is created during application execution:

FTransform SpawnTM;
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnParams.Owner = GetOwner();
/* Attempt to assign an instigator (used for damage application) */
SpawnParams.Instigator = Cast(GetOwner());
ASEquippableActor* NewItem = GetWorld()->SpawnActor(NewItemClass, SpawnTM, SpawnParams);

There are many ways to gain access to actors. Usually you will have a pointer / link to the specific actor that you need. In the example shown above, we save the pointer to the actor of the object being worn in a variable and begin to manipulate an instance of the actor through it.

A very useful function that can be used when prototyping or mastering the engine is UGameplayStatics :: GetAllActorsOfClass (...) . It allows us to get an array of all the actors of the transferred class (including the generated classes; if you pass as an Actor class, then we will get ALL level objects). This function is often feared and avoided as a not-so-effective way of interacting with the environment, but sometimes it is the only tool available.

Actors do not have their own transference, rotation, or scale. All this is set and obtained using the RootComponent, i.e. the top-level component in the SceneComponents hierarchy (more on SceneComponents below). Most commonly used functions like MyActor-> GetActorLocation () actually go to the RootComponent and return its location in the world.

Here are some more useful features that are used in the context of an actor:

  • BeginPlay // The "first" function, called after the creation and full initialization of the actor. This is a convenient place to set the basic logic, timer and make changes to properties, because the actor is already fully initialized and can fulfill requests to his environment.
  • Tick // Called in every frame. For most actors, you can disable it for performance reasons, but it is enabled by default. Great for quickly setting up dynamic logic and checking conditions in each frame. Gradually, you will begin to move more and more code of event-related logic from timers to logic operating at lower frequencies.
  • EndPlay // Called when an actor is removed from the world. Contains "EEndPlayReason", which indicates the reason for the call.
  • GetComponentByClass // Finds one instance of a component of a particular class. It is very useful when you do not know the exact type of actor, but know that it must contain a certain type of component. There is also a GetComponentsByClass that returns all instances of the class, not just the first one found.
  • GetActorLocation // And all its variations are * Rotation, * Scale, including SetActorLocation , etc.
  • NotifyActorBeginOverlap // Convenient for checking overlays caused by any of its components. In this way, you can quickly configure gameplay triggers.
  • GetOverlappingActors // Finds which other actors intersect with the selected one. There is also an option for components: GetOverlappingComponents

Actor contains huge functionality and a lot of variables - it is the foundation of the core of the gameplay in Unreal Engine, so this is not surprising. For further study of this class, it would be nice to open the Actor.h header file in Visual Studio and see what functionality it has. In the article, we still have a lot to consider, so let's move on to the next class in the list.

Actorctorponent


The components are located inside the actors; the standard components are StaticMeshComponent, CharacterMovementComponent, CameraComponent and SphereComponent. Each of these components processes its own particular task, for example, movement, physical interaction (for example, the amount of collision to clearly check interacting actors) or visually displays something in the world, for example, a player’s mesh.

A subclass of this component is SceneComponent - this is the base class for everything related to Transform (Position, Rotation, Scale) that supports attachment. For example, we can attach a CameraComponent to a SpringArmComponent to configure a third-party camera. Correct positioning of the relative location requires both transform and attachment.

Most often, components are created in the constructor of an actor, but you can also create and destroy them at runtime. To begin, let's look at one of the constructors of my actor.

ASEquippableActor::ASEquippableActor()
{
PrimaryActorTick.bCanEverTick = true;
MeshComp = CreateDefaultSubobject(TEXT("MeshComp"));
MeshComp->SetCollisionObjectType(ECC_WorldDynamic); // Just setting a property on our component
RootComponent = MeshComp; // Set our root component for other SceneComponents to attach to
ItemComp = CreateDefaultSubobject(TEXT("ItemComp")); // not attached to anything (not a SceneComponent with a transform)
}

USkeletalMeshComponent is created using CreateDefaultSubobject(actor function) and requires a name (this name can be seen in the list of blueprint components). If you write the game code in C ++, you will often use this function, but ONLY inside the constructor context.

You may also notice that we are defining MeshComp as the new RootComponent. Now all Scene Component should be attached to this mesh, which can be easily done using the following line:

WidgetComp = CreateDefaultSubobject(TEXT("InteractWidgetComp"));
WidgetComp->SetupAttachment(MeshComp);

SetupAttachment will handle the initial attachment; it is expected that it will be called in the constructor for ALL components of the scene, except for the RootComponent itself. You may wonder why my ItemComponent does not call this SetupAttachment function. It just happened because this component is an ActorComponent, but NOT a SceneComponent and does not have a Transform (position, rotation, scale), and therefore should not be added to the hierarchy. However, the component will still register with Actor. Being separated from the hierarchy means that functions like MyActor-> GetComponentByClass will return all ActorComponents and SceneComponents.

Along with Actor, these components are crucial for creating a game in both C ++ and blueprints. They are the building blocks of the game. You can easily create your own components so that they handle some specific aspects of the game, for example, HealthComponent, which stores health points and responds to the damage received by its parent actor.

Using the code below, you can create your own components at runtime. This is different from the CreateDefaultSubobject behavior used only for constructors.

UActorComponent* SpawnedComponent = NewObject(this, UStaticMeshComponent::StaticClass(), TEXT("DynamicSpawnedMeshCompoent"));
if (SpawnedComponent)
{
SpawnedComponent->RegisterComponent();
}

Here is part of the useful functionality of ActorComponents:

  • TickComponent () // Like the Tick () actor, each frame is executed to process high-frequency logic.
  • bool bIsActive and related functions like Activate, Deactivate, ... Used to completely enable / disable a component (including TickComponent) without destroying the component and removing it from the actor.

To ensure replication of ActorComponent, you must call the SetIsReplicated (true) function, the name of which is slightly different from the actor function. This is only necessary when you need to replicate a specific part of the component logic, for example, a variable during function calls, that is, not all components of the replicated actor need to be replicated.

Playercontroller


This is the base class for a player receiving input from a user. By itself, the PlayerController does not appear visually in the environment; instead, it controls a Pawn instance that defines the visual and physical representation of this player in the world. During the gameplay, the player can have several different Pawn (for example, a vehicle or a fresh copy of Pawn when respawn), and the instance of PlayerController remains the same throughout the level. This is important because at some points PlayerController may not have any Pawn at all. This means that things like opening a menu should be added to the PlayerController, and not to the Pawn class.

In multiplayer games, PlayerController exists only on the client that owns it and on the server. This means that in a 4-player game, the server has 4 player controllers, and each client has only one. It is very important to understand when you need to use variables; if all players require replication of the player variable, then it should not exist in PlayerController, but in Pawn or even in PlayerState (discussed below).

Gaining Access to PlayerControllers

  • GetWorld () -> GetPlayerControllerIterator () // GetWorld is available in any instance of Actor
  • PlayerState-> GetOwner () // the owner of playerstate is of type PlayerController, and you must pass it to PlayerController yourself.
  • Pawn-> GetController () // Set only when the user already owns (i.e. controls) the PlayerController.

This class contains the PlayerCameraManager , which handles the camera’s view targets and transforms, including shaking. Another important class that PlayerController manages is the HUD (discussed below). It is used for rendering on Canvas (now it is not used so often because there is UMG) and it can be used to manage data that must be transferred to the UMG interface.

When a new player connects to GameMode, a PlayerController is created for this player in the GameModeBase class using Login ().

Pawn


It is a physical and visual representation of what a player (or AI) controls. It can be a car, a warrior, a tower, or anything that represents a character in a game. The standard Pawn subclass is Character, which implements SkeletalMesh and, more importantly, CharacterMovementComponent, with many options for fine-tuning the player’s movement around the environment using regular shooter movement.

In multiplayer games, each instance of Pawn is replicated to other clients. This means that in a 4-player game, there are 4 pawn instances on the server and on each client. Quite often, a Pawn instance is “killed” when a player dies, and when a respawn is created, a new instance is created. Keep this in mind when storing data that should be preserved after the player’s life ends (or completely discard this pattern and constantly leave the pawn instance alive)
Accessing Pawn

  • PlayerController-> GetPawn () // Only when PlayerController owns Pawn
  • GetWorld () -> GetPawnIterator () // GetWorld is available for any instance of Actor and returns ALL Pawn, including for AI.

Creature

GameModeBase creates Pawn using SpawnDefaultPawnAtTransform. The GameModeBase class also determines which Pawn class to create.

GameModeBase


The base class that defines the classes used (PlayerController, Pawn, HUD, GameState, PlayerState). Often used to set game rules in modes such as “Capture the Flag”; It can handle flags or waves of enemies. Handles other important features, such as creating a player.

GameMode is a subclass of GameModeBase. It contains a few more features that were originally used in Unreal Tournament, such as MatchState and other shooter features.

In multiplayer mode, the GameMode class exists only on the server! This means that no client has an instance of it. In single-player games, he has no influence. To replicate the functions and store the data necessary for GameMode, you can use GameState, which exists on all clients and was created specifically for this purpose.

Gaining access to GameMode

  • GetWorld () -> GetAuthGameMode () // GetWorld is available for any instance of Actor.
  • GetGameState () // returns gamestate for replicating functions and / or variables
  • InitGame (...) // initializes some of the rules of the game, including those specified in the URL (for example, “MyMap? MaxPlayersPerTeam = 2”), which can be transmitted when loading levels in the game.

Hud


This is the user interface class. It contains a lot of Canvas code, which is the code for rendering the user interface written before the advent of UMG . Today, the main work on rendering the user interface is done by UMG.

The class exists only in the client. Replication is not possible. It is owned by PlayerController.

Accessing the HUD

PlayerController-> GetHUD () // Available in the local PlayerController.

Creature

Created using SpawnDefaultHUD (creates a regular AHUD) inside the PlayerController, which owns the HUD, and then redefined by GameModeBase using InitializeHUDForPlayer with the HUD class specified in GameModeBase.

My personal notes

I began to use this class less and less, and I use UMG, which can be controlled through PlayerController. Do not forget - before creating widgets in multiplayer games you need to make sure that the player’s controller is IsLocalController ().

World


UWorld is a top-level object that represents a map on which actors and components will exist and render. It contains a constant level and many other objects, such as gamestate, gamemode, as well as lists of Pawns and Controllers on the map.

Line tracing and all its variations are performed through World using functions such as World- > LineTraceSingleByChannel and many other similar variations.

Gaining access to the World

To get access, just call GetWorld () inside the actors.

When you need to get an instance of World in static functions , you need to pass WorldContextObject, which is essentially a word for any actor that can be used to call -> GetWorld (). Here is an example from one of my header files:

static APlayerController* GetFirstLocalPlayerController(UObject* WorldContextObject);

Gameinstance


GameInstance has one instance that continues to exist throughout the duration of the game. When navigating between maps and menus, the same instance of this class will be saved. This class can be used to create event handlers or process network errors, load user data such as game parameters and functions that are not only related to one level of the game.

Gaining access to GameInstance

  • GetWorld () -> GetGameInstance(); // where T is the class type, for example GetGameInstance() or you own type generated.
  • Actor-> GetGameInstance ()

My personal notes

Usually not used in the early stages of a project. Doesn’t do anything critical unless you delve into development (can manage aspects such as game sessions, playing demos, or transferring data between levels)

Playerstate


A container for variables replicated between a client / server for an individual player. In multiplayer games, it is not designed to execute logic and is just a data container, because PlayerController is not available to all clients, and Pawn is often destroyed when the game dies, so it is not applicable for data that should be stored after death.

Accessing PlayerState

Pawn contains it as a variable Pawn-> PlayerState, also available in Controller-> PlayerState. PlayerState in Pawn is assigned only when Pawn owns the Controller, otherwise nullptr.

A list of all available instances of PlayerState (for example, all players in a match) can be obtained through GameState-> PlayerArray.

Creature

The creating class is assigned in GameMode (PlayerStateClass) and created in AController :: InitPlayerState ()

My personal notes

Useful only when working on multiplayer games.

Gamestatebase


Similar to PlayerState, but provides customers with GameMode information. Since the GameMode instance does not exist in clients, but only on the server, this class is a useful container for replicating information, for example, about the end of match time, team points, etc.

It has two variations - GameState and GameStateBase. GameState handles additional variables required by GameMode (unlike GameModeBase)

Gaining Access to GameStateBase

  • World-> GetGameState() // where T is the called class, for example GetGameState()
  • MyGameMode-> GetGameState () // is stored and available in the gamemode instance (required only on the server that owns the only GameMode instance); customers must use the above call.

My personal notes

Use GameStateBase instead of GameState only if gamemode is not inherited from GameMode instead of GameModeBase.

Uobject


The base object for almost everything in the engine. Actors are inherited from UObject, as well as other base classes, such as GameInstance. It should never be used for rendering, but it is very useful for storing data and functions when struct is not suitable for your requirements.

Creating UObjects

UObjects are not spawned like actors, but created using NewObject(). For instance:

TSubclassOf ClassToCreate;
UObject* NewDesc = NewObject(this, ClassToCreate);

Personal notes

Маловероятно, что вы будете создавать классы непосредственно из UObject, если только вы не хорошо освоили движок и не хотите углубиться в создание собственных систем. Я, например, использую его для хранения списков информации из базы данных.

Его можно использовать для сети, но требуется дополнительная настройка класса объектов, а объекты должны храниться в акторе.

GameplayStatics


Статические классы используются для обработки различного стандартного функционала игр, например, воспроизведения звуков и создания эффектов частиц, создания акторов, применения урона к акторам, получения Pawn игрока, PlayerController и т.д. Этот класс очень полезен для всевозможного доступа к геймплейным функциям. Все функции являются статическими, то есть вам не требуется указатель на экземпляр этого класса и вы можете вызывать функции напрямую из любого места, как это показано в примере ниже.

Получение доступа к GameplayStatics

Так как GameplayStatics является UBlueprintFunctionLibrary, вы можете получить к нему доступ из любого места кода (или блюпринта)

UGameplayStatics::WhateverFunction(); // static functions are easily accessed anywhere, just include #include "Kismet/GameplayStatics.h"

Мои личные примечания

This class has many useful features and you definitely need to know it when creating any game. I recommend to study it to find out about its capabilities.

References


Recommended for learning the basics of gameplay and programming in Unreal Engine 4 materials.


Also popular now: