変数、タイマー、イベント

変数と関数をエディタに公開、タイマーの使用、C++ の関数をブループリントでオーバーライドします。

Windows
MacOS
Linux

このチュートリアルでは、変数と関数のエディタへの公開方法、タイマーを利用したコード実行の遅延またはリピート方法、アクタ間のコミュニケーションでのイベント使用方法を説明します。

1.タイマーを使用したアクタの作成

Unreal Engine 4 にまだ慣れていない場合は、最初に プログラミング クイック スタート ガイド tutorial をお読みください。このチュートリアルでは、プロジェクトの作成とプロジェクトに C++ コードを追加できることを前提にしています。

  1. まず「HowTo_VTE」という名前のスターターコンテンツを使用して、新規で Basic Code (基本コード) プロジェクトを作成し、 Actor クラスを追加します。このチュートリアルでは「Countdown」と名前を付けます。

    ChooseParentClass.png

    NameYourActor.png

  2. ゲームに表示するシンプルなカウントダウンタイマーの作成から始めます。「Countdown.h」ファイルで、クラス定義の最後に以下の行を追加します。

    int32 CountdownTime;
    UTextRenderComponent* CountdownText;
    void UpdateTimerDisplay();
  3. Countdown.cpp に、レンダリング可能なテキスト Component を作成して、カウントダウン時間を 3 秒に初期化します。このタイプの アクタ には必要ないため、Ticking をオフにすることもできます。まず最初にファイルの一番上にコンポーネント用のヘッダを追加します。"include" セクションはこのようになります。

    #include "GameFramework/Actor.h"
    #include "Components/TextRenderComponent.h"
    #include "Countdown.generated.h"

    ヘッダをインクルードすると、ACountdown::ACountdown を書き込むことができます。このようになります。

    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 はテキスト表示を更新して、残り時間、または時間切れの場合はゼロを表示します。ゲームに ACountdown を初めてスポーンした時にこのコードを実行して、 CountdownTime 変数がゼロになるまで 1 秒に一回コードを実行します。

    void ACountdown::UpdateTimerDisplay()
    {
        CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0)));
    }
  5. 関数の実行に Timer を割り当てると、必ず Timer Handle が与えられます。カウントダウン終了時に Timer をシャットダウンできるようにこのハンドルをしっかりと維持します。時間をカウントダウンする関数と、この関数をコントロールするために必要な Timer Handle を Countdown.h のクラス定義に追加しましょう。この作業を行うついでに、カウントダウン終了時に何か特別なことを行う関数も追加しましょう。

    void AdvanceTimer();
    
    void CountdownHasFinished();
    
    FTimerHandle CountdownTimerHandle;

    Countdown.cpp ファイルに ACountdown::AdvanceTimerACountdown::CountdownHasFinished のボディを記述することもできます。

    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. 新しく更新した関数の呼び出しを追加して ACountdown::BeginPlay のテキスト表示を初期化します。そして 1 秒に一回カウントダウンを進め、更新を行うタイマーを設定します。

    UpdateTimerDisplay();
    GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);

    ACountdown::ACountdown ではなく、ACountdown::BeginPlay の表示を更新しています。Unreal Editor で変数に設定されている値は、コンストラクタの後で、BeginPlay の前に割り当てられるからです。後でエディタへ CountdownTime を公開する時にこれらの値に従います。

  7. Unreal Editor へ移動して Compile (コンパイル) を押して、これまでの作業進捗を確認しましょう。

    CompileFromEditor.png

    更新した ACountdown クラスを、コンテンツ ブラウザ から レベル エディタ へドラッグできます。

    ClassInContentBrowser.png

    LevelEditorText.png

    カウントダウン テキストは ACountdown::ACountdown ではなく ACountdown::BeginPlay 中に設定するため、デフォルトの "Text" が レベルエディタ に表示されます。

    Play (再生) を押すと、予定通りカウントダウンが始まって、 "3"、 "2"、 "1"、 最後に "GO!" となります。

この時点で、タイマーを使用するシンプルなクラスが出来上がりました。プログラミングをしないユーザーは、カウントダウン時間を設定したり、カウントダウン終了時の挙動を変更するとさらに活用できます。次にこうした機能をエディタに公開します。

開発途中のコード

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. 変数と関数をエディタに公開する

  1. 現時点のカウントダウン タイマーは値 3 (秒) にハード コーディングされています。エディタでカウントダウン時間に希望する値を設定できるとより実用的になります。しかもこの変更は簡単にできます。Visual Studio で、「Countdown.h」ファイルを開いて以下の行を探します。

    int32 CountdownTime;

    この変数を Unreal Engine に公開するには、UPROPERTY に変更する必要があります。変更後は、ゲームの起動時や保存レベルのロード時に変数の値をエンジンに保存できるようになります。影響を与える変数のすぐ上に以下のように空の丸括弧を付けて UPROPERTY タグを追加します。

    UPROPERTY()
    int32 CountdownTime;

    UPROPERTY は Unreal Engine がどのように変数を使用するかを変更する引数をサポートします。変数を編集可能に設定したいため、EditAnywhere 引数を追加します。

    UPROPERTY(EditAnywhere)
    int32 CountdownTime;

    ExposingVariable.png

    C++ コードの変数にコメントを追加することもできます。コメントは以下のように Unreal Editor で変数の説明として表示されます。

    //How long, in seconds, the countdown will run
    UPROPERTY(EditAnywhere)
    int32 CountdownTime;

    CommentingVariable.png

    UPROPERTY でさらに多くの設定が可能です。BlueprintReadWriteCategory など他の指定子を今後調査すると良いでしょう。現時点で必要なものはそろっています。

    Unreal Editor に戻って [Compile (コンパイル)] ボタンを押すと、配置した ACountdown の変数が [Details(詳細)] パネルに表示されます。タイマー値を変更して [Play] を押して様々な値で試してみてください。

  1. タイマー値の変更に加えて、プログラマーでない方もタイマー終了時に起こることを変更できるようにしましょう。Visual Studio で Countdown.h ファイルを開いて以下の行を探します。

    void CountdownHasFinished();

    この関数を以下のように UFUNCTION にすると Unreal Engine にこの関数を公開できます。

    UFUNCTION()
    void CountdownHasFinished();

    UPROPERTY マクロのように、より多くの機能を有効にしたりプログラマーではないデベロッパーが利用できるように、何ができるかについての情報を追加する必要があります。考えられるオプションは以下の 3 つです。

    1. BlueprintCallable 関数は C++ コードで記述されていて ブループリントグラフ で呼び出しが可能ですが、C++コードを編集せずに変更またはオーバーライドすることができません。このようにマークされた関数は、通常はプログラマー以外の方が使用するためにプログラミングされた機能ですが、変更を想定していなかったり、変更する意味がありません。例えば分かりやすい例として math 関数を見てみましょう。

    2. BlueprintImplementableEvent 関数は C++ ヘッダ (".h") ファイルに設定されていますが、関数のボディ全体は C++ コードではなくブループリント グラフに記述されます。期待していたデフォルト アクションや標準のビヘイビアが存在しないなど、特殊な状況でプログラマー以外の方がカスタム仕様のリアクションを作成して対応できるようになっています。この例としては、スペースシップゲームでパワーアップがプレイヤーのシップに触れた時のイベントなどがあります。

    3. BlueprintNativeEvent 関数は BlueprintCallable 関数と BlueprintImplementableEvent 関数の組み合わせのようなものです。デフォルトのビヘイビアは C++ でプログラミングされていますが、ブループリント グラフ でオーバーライドして追加や置換が可能です。こうしたプログラミングをする場合、以下のように C++ コードは名前の最後に _Implementation を追加して常に仮想関数にします。これが最も柔軟性のあるオプションなため、このチュートリアルではこのオプションを使用します。

    プログラマー以外の方が C++ 関数を呼び出したり ブループリント でオーバーライドできるように、Countdown.h ファイルに以下の変更を加えます。

    UFUNCTION(BlueprintNativeEvent)
    void CountdownHasFinished();
    virtual void CountdownHasFinished_Implementation();

    「Countdown.cpp」で次の行を探します。

    void ACountdown::CountdownHasFinished()

    変更後:

    void ACountdown::CountdownHasFinished_Implementation()

C++ コードに独自の値と機能を記述しながら、プログラマー以外の方がアクセスおよび変更可能な変数と関数を作成しました。プログラマー以外の方の使用方法を確認するために、ACountdown クラスのブループリント拡張を作成して自身でこれを修正します。

完成コード

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. ブループリントで C++ コードを拡張およびオーバーライドする

このチュートリアルでは、 ブループリント を使用した C++ クラスの機能拡張方法を紹介します。ブループリントのチュートリアルとしてではなく、C++ コードの記述にミスがないことを確認するテストを目的としています。ブループリント の導入に関しては ブループリント クイックスタートガイド を一読することをお勧めします。

  1. 「Countdown1」と名付けられた ACountdown インスタンスのビヘイビアを変更するには、まず編集可能な「Countdown1」のブループリント バージョンをエディタで作成しなくてはいけません。これを行うには、ワールド アウトライナー で「Countdown1」を選択して [Details (詳細)] パネル[Blueprint/Add Script] ボタンをクリックします。

    AddScript.png

    ここで修正する ACountdown クラスを格納する Blueprint アセットにパスを割り当てて名前を付けることができます。

    SelectBlueprintPath.png

    "Countdown1" のブループリント バージョンを表す新規アセットが作成されます。Countdown1 もこの新規ブループリントのインスタンスと置き換えられるため、ブループリントを変更するとゲーム内の Countdown1 に影響を及ぼします。

  2. Unreal Editorコンテンツ ブラウザ に表示される新規アセットへ自動的に移動します。アセットを 右クリック し、[Edit (編集) ...] を選択して、 Blueprint Graph (ブループリントグラフ)Component (コンポーネント) 階層、 Default Values (デフォルト値) を修正します。

    BlueprintInContentBrowser.png

    EditBlueprint.png

  3. 関数とイベントは [Event Graph] タブに表示されます。最初にこれを選択します。

    SelectEventGraph.png

    Event Graph ウィンドウの任意の場所を 右クリック して、ビヘイビアを決定するイベントノードとして CountdownHasFinished 関数を追加することができます。

    SelectEvent.png

  4. 新規ノードの右側にある白い (実行) ピンを左クリックしてドラッグし、機能を追加することができます。

    DragExecPin.png

    左マウス ボタンを放すと、実行したい関数またはイベント名の入力が促されます。このチュートリアルでは、カウントダウン終了時に パーティクル システム をスポーンしましょう。Spawn Emitter At Location ノードが必要なので、このノードをリストから選択します。例えば「spawn loc」など名前の一部を検索欄に入力すると時間を短縮できます。次に黄色の「Location」ピンを左クリックしてドラッグし、 Get Actor Location 関数にアタッチします。

    GetActorLocation.png

    後は作成したいエフェクトを選択するだけです。Emitter Template で Select Asset をクリックすると、適切なエフェクト アセットのリストを取得できます。この場合、P_Explosion が適切であるため、これを選択します。

    SelectParticle.png

  5. ブループリント エディタ の左上にある [Compile (コンパイル)] ボタンをクリックして変更を保存します。

  6. ここで [Play (再生)] を押すと、カウントダウンが始まり、カウントダウン数がゼロになると爆発します。

    Explosion_Zero.png

    しかし、カウントダウンは最後に「0」ではなく「GO!」となるようにプログラミングしました。C++ の機能を ブループリント ビジュアルスクリプティングに完全に置きかえたため、これはもう起こりません。この状況は意図したものではないため、この関数の C++ バージョンへの呼び出しを追加しなくてはいけません。Countdown Has Finished ノードを右クリックして、コンテキスト メニューから Add call to parent function を選択します。

    CallToParent_Menu.png

    この作業が終了すると Parent:Countdown Has Finished とラベル付けされたノードが イベントグラフ に配置されます。Parent ノードを接続する典型的な場所は、イベントノードへの直接接続です。Parent-call ノードはその他のノード同様に、何度でもどこからでも呼び出すことができるため、これは必須ではありません。

    CallToParent_ConnectPins.png

    これにより Spawn Emitter At Location への接続が置換されるため、 Parent:Countdown Has Finished ノードの右側 (出力) にある実行ピンを Spawn Emitter At Location に接続する必要があります。接続しなければ実行しません。

    CallToParent_FixPins.png

    これでゲームの実行時にカウントダウンが終了すると "GO!" (C++ コードから) という文字と爆発 (ブループリント グラフから) が表示されます。

    Explosion_Go.png

4.応用編

ここまでで学んだ知識を活かして、以下を行ってみます。

  • イベント 実行時にターゲットのトランスフォームへ移動または回転する アクタ を作成します。ゲームでプラットフォームやドアを移動するために使用します。このイベントが 2 番目のイベントをトリガーする タイマー を開始するようにします。この 2 番めのイベントはアクタを元の位置に戻します。ハードコードされた値の代わりに公開された変数 (UPROPERTY を用いて) を適切な場所に使用します。

  • タイマー ハンドルといくつかのカスタム イベント を使用して、燃え尽きる (火の Particle System Component を非アクティブにするなど) 火のついたトーチを作成します。例えば AddFuel Event でトーチの燃焼時間を延長することができます。DouseWithWater Event はトーチを瞬時に消火して、今後 AddFuel が機能しないように設定します。こうした機能は両方とも ティック を使用しないで記述できます。ハンドルで実行しているタイマーを単に修正するだけです。

以下はこのチュートリアルの内容の詳しい情報のリンク先です。

Select Skin
Light
Dark

Welcome to the new Unreal Engine 4 Documentation site!

We're working on lots of new features including a feedback system so you can tell us how we are doing. It's not quite ready for use in the wild yet, so head over to the Documentation Feedback forum to tell us about this page or call out any issues you are encountering in the meantime.

We'll be sure to let you know when the new system is up and running.

Post Feedback