Variables, Timers, and Events

Expose variables and functions to the editor, use timers, and override C++ functions with Blueprints.

Choose your operating system:

Windows

macOS

Linux

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

Unreal Engine 4 , you might want to read our Programming Quick Start tutorial

  1. 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.

    ChooseParentClass.png

    NameYourActor.png

  2. 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();
  3. 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;
    }
  4. 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 our ACountdown into the game, and once per second until our CountdownTime variable hits zero.

    void ACountdown::UpdateTimerDisplay()
    {
        CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0)));
    }
  5. 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 and ACountdown::CountdownHasFinished in Countdown.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!"));
    }
  6. 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);

    ACountdown::BeginPlay rather than ACountdown::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 expose CountdownTime

  7. Let's check our progress so far by going to the Unreal Editor and pressing Compile .

    CompileFromEditor.png

    We can then drop our updated ACountdown class from the Content Browser into the Level Editor .

    ClassInContentBrowser.png

    LevelEditorText.png

    ACountdown::BeginPlay and not ACountdown::ACountdown , the default Text is shown in the Level Editor 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

  1. 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. The UPROPERTY 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 the EditAnywhere argument:

    UPROPERTY(EditAnywhere)
    int32 CountdownTime;

    ExposingVariable.png

    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;

    CommentingVariable.png

    UPROPERTY , and looking into other Specifiers such as BlueprintReadWrite and Category

    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 .

  2. 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:

    1. 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.

    2. 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.

    3. BlueprintNativeEvent functions are like a combination of BlueprintCallable and BlueprintImplementableEvent 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

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

  1. 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 .

    AddScript.png

    From there, we can provide a path and name for the Blueprint asset that will contain our modified ACountdown class.

    SelectBlueprintPath.png

    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.

  2. 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 hierararchy, and Default Values .

    BlueprintInContentBrowser.png

    EditBlueprint.png

  3. Functions and events can be found in the Event Graph tab, so we'll select that first.

    SelectEventGraph.png

    Then, by Right-clicking anywhere in the Event Graph window, we can add our CountdownHasFinished function as an event node to define its behavior.

    SelectEvent.png

  4. 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.

    DragExecPin.png

    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.

    GetActorLocation.png

    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.

    SelectParticle.png

  5. Click the Compile button at the top left of the Blueprint Editor to save the changes.

  6. If we press Play now, we'll see our countdown take place, and our explosion will happen when our countdown number hits zero.

    Explosion_Zero.png

    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.

    CallToParent_Menu.png

    When this is done, a node labeled **Parent: ProgrammingAndScripting/ProgrammingWithCPP/CPPTutorials

    CallToParent_ConnectPins.png

    Note that this will replace the connection to Spawn Emitter At Location , so we'll need to connect our **Parent: ProgrammingAndScripting/ProgrammingWithCPP/CPPTutorials

    CallToParent_FixPins.png

    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!

    Explosion_Go.png

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:

Help shape the future of Unreal Engine documentation! Tell us how we're doing so we can serve you better.
Take our survey
Dismiss