[UE4] チュートリアル時の試行錯誤 (変数、タイマー、イベント)


背景

こちらの変数、タイマー、イベントのチュートリアルを見ながら実装したところ、コンパイルエラーが起きたので、直し方を書きます。

結論

  • GetWorldTimerManager()使う時は#include "TimerManager.h"を追加する
  • UTextRenderComponentなどコンポーネント使う時は、.hファイルにinclude忘れずに
  • .generated.hはincludeヘッダー群の一番最後に書く

コンパイルエラーがでたコード

CppTurorial2というプロジェクトにCountDown.hとCountDown.cppを実装しました。(チュートリアルではCountdown.cppと小文字である)

CountDown.h
// Copyright 1998-2018 Epic Games, Inc.All Rights Reserved.

#pragma once

#include "GameFramework/Actor.h"
#include "CountDown.generated.h"

UCLASS()
class CPPTUTORIAL2_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 "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!"));
}

エラー内容

これを見ると

GetWorldTimerManager()UTextRenderComponentにエラーが出ています。

チュートリアル通りなのになぜ?調べてみた

GetWorldTimerManager()について

不完全な型は使用できません。でググると
https://docs.microsoft.com/ja-jp/cpp/c-language/incomplete-types?view=vs-2019

... わからん、メンバーがまだ指定されていない構造体型。なのか?

GetWorldTimerManager()でググると、

ちゃんと、#include "GameFramework/Actor.h"してるんだけどな...

UE4.20の記事、期待できそう。

サンプルコードを見てみると

#include "TimerManager.h"

お?試しに、CountDown.cppに追加して、ビルドしてみる。

不完全な型は使用できません。がなくなった!

UTextRenderComponentについて

同じく、includeのし忘れを疑ってみる。文字化けしてみれない...

UTextRenderComponentでググる

#include "Components/TextRenderComponent.h"かぁ、そういえばCountDown.hになかったな、追加してみよう。ビルド。

#include "CountDown.generated.h"はincludeヘッダの最後でなければならないらしい

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

ほんとだ

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

これでビルド。

やった正常終了!

再生すると?

ちゃんとGO!が表示されました。

最終コード

残りのチュートリアルもやってみた後のコード

CountDown.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/TextRenderComponent.h"
#include "CountDown.generated.h"

UCLASS()
class CPPTUTORIAL2_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 DeltaTime) 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



// Fill out your copyright notice in the Description page of Project Settings.


#include "CountDown.h"
#include "Components/TextRenderComponent.h"
#include "TimerManager.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) {
        GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
        CountdownHasFinished();
    }
}

void ACountDown::CountdownHasFinished_Implementation() {
    CountdownText->SetText(TEXT("GO!"));

}