This tutorial will show you how to expose variables and functions to the editor, use timers to delay or repeat code execution, and use events to communicate between Actors.
1. Creating an Actor that Uses a Timer
If you are new to Unreal Engine, you might want to read our Programming Quick Start first. For this tutorial, we will assume you are familiar with creating a project and adding C++ code to it.
We will begin by creating a new, Basic Code project, with starter content, named HowTo_VTE, and then adding an Actor class to it. We'll name it Countdown in this tutorial.
We'll start by creating a simple countdown timer that we can see in-game. In Countdown.h, add the following lines to the end of our class definition:
int32 CountdownTime; UTextRenderComponent* CountdownText; void UpdateTimerDisplay();
In Countdown.cpp, we can create our renderable text Component and initialize our countdown time to 3 seconds. We can also turn Ticking off for this type of Actor, since we will not need it. To do this, we must add the header for the Component at the top of the file, making the "include" section look like this:
#include "GameFramework/Actor.h" #include "Components/TextRenderComponent.h" #include "Countdown.generated.h"
With the header included, we can write
ACountdown::ACountdown
. It should look like this:ACountdown::ACountdown() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = false; CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber")); CountdownText->SetHorizontalAlignment(EHTA_Center); CountdownText->SetWorldSize(150.0f); RootComponent = CountdownText; CountdownTime = 3; }
ACountdown::UpdateTimerDisplay
should update our text display to show the time remaining, or zero if the time is up. This code should run when we first spawn ourACountdown
into the game, and once per second until ourCountdownTime
variable hits zero.void ACountdown::UpdateTimerDisplay() { CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0))); }
Whenever we assign a Timer to run a function, we are given a Timer Handle. We need to hold onto that handle so that we can shut the Timer down when the countdown finishes. Let's add the function to count time down, and the Timer Handle we'll need to control it, to the class definition in
Countdown.h
. While we're there, let's also add a function to do something special when the countdown ends:void AdvanceTimer(); void CountdownHasFinished(); FTimerHandle CountdownTimerHandle;
We can also write the body of
ACountdown::AdvanceTimer
andACountdown::CountdownHasFinished
inCountdown.cpp
now:void ACountdown::AdvanceTimer() { --CountdownTime; UpdateTimerDisplay(); if (CountdownTime < 1) { //We're done counting down, so stop running the timer. GetWorldTimerManager().ClearTimer(CountdownTimerHandle); CountdownHasFinished(); } } void ACountdown::CountdownHasFinished() { //Change to a special readout CountdownText->SetText(TEXT("GO!")); }
Let's initialize the text display in
ACountdown::BeginPlay
by adding a call to our new update function, and setting a timer to advance and update the countdown once per second:UpdateTimerDisplay(); GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
We are updating the display in
ACountdown::BeginPlay
rather thanACountdown::ACountdown
because values set to variables in the Unreal Editor will be assigned after the constructor, but before BeginPlay. We will want to respect those values later, when we exposeCountdownTime
to the editor.Let's check our progress so far by going to the Unreal Editor and pressing Compile.
We can then drop our updated
ACountdown
class from the Content Browser into the Level Editor.Because we set our countdown text during
ACountdown::BeginPlay
and notACountdown::ACountdown
, the default Text is shown in the Level Editor.When we press Play, the countdown will progress as expected, saying 3, 2, 1, and finally GO!
At this point, we've already created a simple class that uses a timer. Non-programming users would get much more out of it if they could set the countdown time, or change the behavior when the countdown finishes. Next, we'll expose these features to the editor.
Work-In-Progress Code
Countdown.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/Actor.h"
#include "Countdown.generated.h"
UCLASS()
class HOWTO_VTE_API ACountdown : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ACountdown();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
//How long, in seconds, the countdown will run
int32 CountdownTime;
UTextRenderComponent* CountdownText;
void UpdateTimerDisplay();
void AdvanceTimer();
void CountdownHasFinished();
FTimerHandle CountdownTimerHandle;
};
Countdown.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "HowTo_VTE.h"
#include "Components/TextRenderComponent.h"
#include "Countdown.h"
// Sets default values
ACountdown::ACountdown()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
CountdownText->SetHorizontalAlignment(EHTA_Center);
CountdownText->SetWorldSize(150.0f);
RootComponent = CountdownText;
CountdownTime = 3;
}
// Called when the game starts or when spawned
void ACountdown::BeginPlay()
{
Super::BeginPlay();
UpdateTimerDisplay();
GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
}
// Called every frame
void ACountdown::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
void ACountdown::UpdateTimerDisplay()
{
CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0)));
}
void ACountdown::AdvanceTimer()
{
--CountdownTime;
UpdateTimerDisplay();
if (CountdownTime < 1)
{
// We're done counting down, so stop running the timer.
GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
//Perform any special actions we want to do when the timer ends.
CountdownHasFinished();
}
}
void ACountdown::CountdownHasFinished()
{
//Change to a special readout
CountdownText->SetText(TEXT("GO!"));
}
2. Exposing Variables and Functions to the Editor
Our countdown timer is currently hard-coded to use a value of 3 seconds. It would be more useful if we could set the countdown time to any value we want in the editor, and this is easy to do. In Visual Studio, we can open
Countdown.h
and find the line that says:int32 CountdownTime;
In order to expose this variable to Unreal Engine, we need to make it a
UPROPERTY
. This enables the engine to preserve the value of the variable when launching the game or loading a saved level. TheUPROPERTY
tag, with empty parentheses, is added right above the variable it affects:UPROPERTY() int32 CountdownTime;
UPROPERTY
supports arguments that change how Unreal Engine will use the variable. Since we want our variable to be editable, we can add theEditAnywhere
argument:UPROPERTY(EditAnywhere) int32 CountdownTime;
We can also add a comment to our variable in C++, and our comment will become the description of the variable in the Unreal Editor, like this:
//How long, in seconds, the countdown will run UPROPERTY(EditAnywhere) int32 CountdownTime;
There is a lot more we can do with
UPROPERTY
, and looking into other Specifiers such asBlueprintReadWrite
andCategory
might be good next steps, but we have all that we need at the moment.When we return to the Unreal Editor and press Compile, our variable will appear in the Details Panel for the
ACountdown
we placed earlier, and we can test out different timer values by changing this number and pressing Play.In addition to changing the value of the timer, let's also enable non-programming developers to change what happens when the timer is up. In Visual Studio, we'll open Countdown.h and find the following line:
void CountdownHasFinished();
We can expose this function to the Unreal Engine by making it a
UFUNCTION
, like this:UFUNCTION() void CountdownHasFinished();
Just like the
UPROPERTY
macro, we need to provide information about what can be done with it in order to enable more features and access for non-programming developers. There are three options to consider:BlueprintCallable
functions are written in C++ and can be called from the Blueprint Graph, but cannot be changed or overridden without editing C++ code. Functions marked this way are usually features that have been programmed for non-programmer use, but that are not supposed to be changed or wouldn't make sense to change. An easy example of this would be any kind of math function.BlueprintImplementableEvent
functions are set up in a C++ header (.h) file, but the body of the function is written entirely in the Blueprint Graph, not in C++. These are usually created to give a non-programmer the ability to create custom reactions to special situations that have no expected default action or standard behavior. An example of this might be an event that happens when a powerup touches the player's ship in a spaceship game.BlueprintNativeEvent
functions are like a combination ofBlueprintCallable
andBlueprintImplementableEvent
functions. They have default behaviors programmed in C++, but these can be supplemented or replaced by overriding in the Blueprint Graph. When programming these, the C++ code always goes in a virtual function with _Implementation added to the end of the name, as shown below. This is the most flexible option, so we will use it for this tutorial.
To grant non-programmers the ability to call our C++ function and to override it with Blueprints, we need to make the following changes to
Countdown.h
:UFUNCTION(BlueprintNativeEvent) void CountdownHasFinished(); virtual void CountdownHasFinished_Implementation();
Then, in Countdown.cpp, we will need to change the line that says:
void ACountdown::CountdownHasFinished()
To:
void ACountdown::CountdownHasFinished_Implementation()
We have now made a variable and a function accessible to, and alterable by, non-programmers, while providing our own default value and functionality in C++. To see how a non-programmer might use this, we'll make a Blueprint extension of our ACountdown
class and modify it ourselves.
Finished Code
Countdown.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/Actor.h"
#include "Countdown.generated.h"
UCLASS()
class HOWTO_VTE_API ACountdown : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ACountdown();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
//How long, in seconds, the countdown will run
UPROPERTY(EditAnywhere)
int32 CountdownTime;
UTextRenderComponent* CountdownText;
void UpdateTimerDisplay();
void AdvanceTimer();
UFUNCTION(BlueprintNativeEvent)
void CountdownHasFinished();
virtual void CountdownHasFinished_Implementation();
FTimerHandle CountdownTimerHandle;
};
Countdown.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "HowTo_VTE.h"
#include "Components/TextRenderComponent.h"
#include "Countdown.h"
// Sets default values
ACountdown::ACountdown()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
CountdownText->SetHorizontalAlignment(EHTA_Center);
CountdownText->SetWorldSize(150.0f);
RootComponent = CountdownText;
CountdownTime = 3;
}
// Called when the game starts or when spawned
void ACountdown::BeginPlay()
{
Super::BeginPlay();
UpdateTimerDisplay();
GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
}
// Called every frame
void ACountdown::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
void ACountdown::UpdateTimerDisplay()
{
CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0)));
}
void ACountdown::AdvanceTimer()
{
--CountdownTime;
UpdateTimerDisplay();
if (CountdownTime < 1)
{
// We're done counting down, so stop running the timer.
GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
//Perform any special actions we want to do when the timer ends.
CountdownHasFinished();
}
}
void ACountdown::CountdownHasFinished_Implementation()
{
//Change to a special readout
CountdownText->SetText(TEXT("GO!"));
}
3. Extend and Override C++ with Blueprints
This section of the tutorial involves using Blueprints to extend the functionality of C++ classes. However, it is only intended as a test that our C++ code was written correctly, not as a Blueprint tutorial. For a proper introduction to Blueprints, we recommend the Blueprints Quick Start Guide.
To change the behavior of our ACountdown instance, called Countdown1, in the editor, we must first make an editable Blueprint version of it. To do this, we can select it from the World Outliner and click the Blueprint/Add Script button in the Details Panel.
From there, we can provide a path and name for the Blueprint asset that will contain our modified ACountdown class.
This will create a new asset that represents a Blueprint version of Countdown1. It will also replace Countdown1 with an instance of this new Blueprint, so that changes we make to the Blueprint will affect Countdown1 in the game.
The Unreal Editor will automatically take us to our new asset in the Content Browser, and we can Right-click it and choose "Edit..." to modify its Blueprint Graph, Component hierarchy, and Default Values.
Functions and events can be found in the Event Graph tab, so we'll select that first.
Then, by Right-clicking anywhere in the Event Graph window, we can add our CountdownHasFinished function as an event node to define its behavior.
We can now add any additional functionality we would like by left-clicking and dragging off of the white (execution) pin on the right side of our new node.
When we release the left mouse button, we will be asked what function or event we would like to execute. For this tutorial, let's spawn a Particle System when the countdown finishes. We'll want a Spawn Emitter At Location node, so select that from the list. It can save time to type a partial phrase, like spawn loc, into the search field. We can then left-click and drag the yellow "Location" pin and attach it to a Get Actor Location function.
Now we just need to select what effect we'd like to see. By clicking Select Asset under Emitter Template, we can get a list of appropriate effect assets. P_Explosion is a good one, so we'll pick that.
Click the Compile button at the top left of the Blueprint Editor to save the changes.
If we press Play now, we'll see our countdown take place, and our explosion will happen when our countdown number hits zero.
However, we programmed our countdown to say GO! at the end, not 0. This is no longer happening because we have completely replaced our C++ functionality with our Blueprint visual scripting. This is not what we intended to do in this case, so we need to add a call to the C++ version of this function, which can be done by right-clicking the Countdown Has Finished node and selecting Add call to parent function from the context menu.
When this is done, a node labeled Parent: Countdown Has Finished will be placed in the Event Graph. The typical place to connect a parent node is directly to the event node, which is what we will do here. This is not required, though, as the parent-call node is like any other and can be called anywhere we like, even multiple times.
Note that this will replace the connection to Spawn Emitter At Location, so we'll need to connect our **Parent: programming-and-scripting/programming-language-implementation/cpp-in-unreal-engine/unreal-engine-cpp-tutorials
Now when we run our game, we should see both the word GO! (from our C++ code) and an explosion (from our Blueprint Graph) after the countdown finishes!
4. On Your Own!
Using what you have learned, try to do the following:
Create an Actor that moves or rotates to a target transform when an Event is run. This could be used as a moving platform or door in a game. Make the Event start a Timer that will trigger a second Event which moves the Actor back to its original location. Use exposed variables (that is, exposed through
UPROPERTY
) instead of hard-coded values wherever appropriate.Make a lit torch that burns out (perhaps by deactivating a fiery Particle System Component) by using a Timer handle and a few custom Events. For example, an AddFuel Event could extend the burning time of the torch. A DouseWithWater Event could shut it off immediately and prevent AddFuel from working in the future. Both of these features can be written without using a Tick, simply by modifying a running Timer through its handle.
As for the specifics covered in this tutorial:
For more information about Timers, try the Gameplay Timers page.
For a complete reference using the
UPROPERTY
tag with variables in your classes or structs, look at the Properties page.To learn more about
UFUNCTIONS
and Event creation, check the UFunctions page.For further tutorials, see the C++ Programming Tutorials page.