[UE4] ブロック崩しを作る #6 スコアUI (C++)

# はじめに
この記事は UE4 でブロック崩しを作るシリーズ の続きになります。
今回はゲームにスコアを表示する UI を追加します。
UI の表示には **UMG** (Unreal Motion Graphics) を使用しますが、表示処理には BP のイベントグラフは使わず C++ で実装してゆきます。

# 完成ゲーム
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/6626af09-b266-ae68-fbd2-86c80a067b90.png)


# 環境
Windows 10
Unreal Engine 4.27.1
エディタ言語 英語

# 目次
[スコア表示用の WBP を作成](#スコア表示用の-wbp-を作成)
[WBP を操作する UUserWidget(C++) の作成](#wbp-を操作する-uuserwidgetc-の作成)
[WBP と UUserWidget(C++) を繋げる](#wbp-と-uuserwidgetc-を繋げる)
[WBP を配置](#wbp-を配置)
[実行](#実行)
[次回](#次回)
[おまけ](#おまけ)


# スコア表示用の WBP を作成

スコアを表示する WBP(Widget Blueprint) を作成します

Content Browser で右クリック
開いたメニューから User Interface → 「Widget Blueprint」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/5d02d43b-8a42-2410-ae0f-49a31c5313aa.png)

作成された NewWidgetBlueprint を「WBP_Score」にリネームします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/34bbacd1-b223-a8e0-638e-703aead387dd.png)

## WBP 編集
「WBP_Score」をダブルクリックして BP 編集画面を開きます。
下記の [UMG UI デザイナ](https://docs.unrealengine.com/4.27/ja/InteractiveExperiences/UMG/) が開きます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/c7fa0523-78e8-7972-0121-35fca8624b8d.png)

左にある Palette から「Text」を選択してメインのビューにドラッグ＆ドロップ
この UI にテキスト表示が追加されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/3642a18c-9bfe-9d5d-5524-3c1557dde7c3.png)

追加したテキストの内容を編集します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/ac6c13a8-a870-1d6c-9892-9c67761fa58b.png)

* 一番上の TextBlock_0 はデフォルトでつけられたこのテキストアイテムの名前です
* 位置を左上に変更します。画面上でドラッグして移動も出来ます
* 表示する内容を「SCORE 0」に変更します
* フォントサイズを 50 くらいにしておきます

# WBP を操作する UUserWidget(C++) の作成

上部メニューから File → New C++ Class.. を選択
開いたウィンドウから
① 右上の Show All Classes にチェックを入れ 
② **UUserWidget** を検索して選択
③ Next をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/7f170020-6f3c-c086-fa52-91716edabc60.png)

名前を「UW_Score」として Create Class をクリック
Visual Studio が起動して プロジェクトに UW_Score.h /.cpp が追加されています

## UUserWidget(C++) 編集

作成された UW_Score.h / .cpp を編集します

```C++:UW_Score.h
#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/TextBlock.h" // 追加
#include "UW_Score.generated.h"

UCLASS()
class BREAK_BLOCK_API UUW_Score : public UUserWidget
{
	GENERATED_BODY()

protected:
	UTextBlock* m_pTextBlock;

public:
	UUW_Score(const FObjectInitializer& ObjectInitializer);

protected:
	virtual void NativeOnInitialized() override;

public:
	void UpdateScore(int score);
};
```
* **UTextBlock* m_pTextBlock** を追加
  * 使う度に取得する必要のないように WBP に追加したテキスト表示のポインタを記録する変数 
* **コンストラクタ** 追加
* **void NativeOnInitialized()** を override;
  * 初期化時に呼ばれるので、初期化用に使用
* **void UpdateScore(int score)** を追加
  * 外部からスコアをセットしてもらいその数値に表示を更新する関数


```C++:UW_Score.cpp
#include "UW_Score.h"

// コンストラクタ
UUW_Score::UUW_Score(const FObjectInitializer& ObjectInitializer)
	: UUserWidget(ObjectInitializer)
	, m_pTextBlock(nullptr)
{
}

// 初期化、Text へのポインタを記録
void UUW_Score::NativeOnInitialized()
{
	Super::NativeOnInitialized();

	UTextBlock* pTextBlock = Cast<UTextBlock>(GetWidgetFromName("TextBlock_0"));
	ensure(pTextBlock != nullptr);
	m_pTextBlock = pTextBlock;
}

// スコアUI の Text を更新
void UUW_Score::UpdateScore(int score)
{
	FString str = TEXT("Score") + FString::FromInt(score);
	ensure(m_pTextBlock != nullptr);
	if(m_pTextBlock)
	{
		m_pTextBlock->SetText(FText::FromString(str));
	}
}
```

* **void NativeOnInitialized()** 
  * テキストの名前を **TextBlock_0** にしていたので、その名前でテキストを取得
  * 毎回取得する必要のないように m_pTextBlock に保存
* **void UpdateScore(int score)**
  * m_pTextBlock のテキスト表示を更新するには **FText** の文字列をセットする必要がある
  * **FText** は文字列の加工処理が苦手なので、一旦 **FString** で文字列を加工して最後に変換してセットしている

# WBP と UUserWidget(C++) を繋げる

* WBP_Score の編集画面を開きます
* 右上の「Graph」を選択します
* 上部の「Class Settings」を選択します
* Details から Parent Class を**「UW_Score」**に変更します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/38ba7c15-be9a-ece7-79ad-5fc6633091e4.png)


# WBP を配置

WBP を配置するには誰かが **CreateWidget** で実体を作り **AddToViewport** で画面に表示させる必要があります。今回はそれを行う専用の「Actor」を作成し、WBP が配置されるようにします。

## WBP を配置する Actor を作成

* 上部メニューから File → New C++ Class.. を選択
* Actor を選択して Next をクリック
* Name を「AUWScoreController」として Create Class をクリック
* Visual Studio を reload して「AUWScoreController.h / .cpp」が追加されているのを確認

## WBP を配置する Actor の編集

AUWScoreController.h / .cpp を編集します

```C++:AUWScoreController.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "UW_Score.h" // 追加
#include "AUWScoreController.generated.h"

UCLASS()
class BREAK_BLOCK_API AAUWScoreController : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AAUWScoreController();

protected:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UMG_Game")
	TSubclassOf<UUserWidget> WBP_Score;

	UUW_Score* m_pUWScore;

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
    :
```

* **TSubclassOf<UUserWidget> WBP_Score** を追加
  * 作成する UUserWidget を 編集画面から受け取る用
* **UUW_Score* m_pUWScore** を追加
  * 配置した UMG のポインタを保存する


```C++:AUWScoreController.cpp
:
// Called when the game starts or when spawned
void AAUWScoreController::BeginPlay()
{
	Super::BeginPlay();
	
	m_pUWScore = CreateWidget<UUW_Score>(GetWorld(), WBP_Score);
	ensure(m_pUWScore);
	if (m_pUWScore)
	{
		m_pUWScore->AddToViewport();
	}
}
```

* WBP_Score には作成すべき UUserWidget が指定されている前提で **CreateWidget()** で Widget を作成
* 作成した widget を **AddToViewport** で表示

これで WBP を配置する Actor が作成できました
この Actor を配置すれば BeginPlay() が呼ばれ WBP が表示されます


## WBP を配置する Actor を配置

Contents Browser を C++ Classes → BreakBlock の表示に切り替え
中にいる AUWScoreController をビューポートにドラッグ＆ドロップで配置します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/af3493fb-c39b-1e43-6086-ecf0b02a9aa6.png)

World Outliner に AUWScoreController が追加されています
それを選択して Details から **WBP_Score** をセットします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/049f4b90-1414-ed05-ef20-6e6eea3dbe54.png)
※ この Actor の .h に下記を追加していたので、このように Details から値をセットする事が可能です

```C++
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UMG_Game")
TSubclassOf<UUserWidget> WBP_Score;
```

# 実行

プレイをするとゲーム画面左上にスコアが表示されるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/6626af09-b266-ae68-fbd2-86c80a067b90.png)

## 値の変化

この表示を変化させる UpdateScore(int score) を UUW_Scoreクラスに作成しましたが
まだ誰もこの関数を呼んでいませんでした。
下記のように AAUWScoreControllerクラス から関数を呼ぶようにします。

```C++:AUWScoreController.cpp
void AAUWScoreController::BeginPlay()
{
    :
	if (m_pUWScore)
	{
		m_pUWScore->AddToViewport();
		m_pUWScore->UpdateScore(30); // 追加
	}
}
```

表示されるテキストが「Score30」に変化しました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/fbfdd906-a65c-62a2-2eb5-42c34965851d.png)


# 次回
スコアUIが表示されるようになりました。
ただ、ブロックを破壊してスコアが加算される、という処理ができておりません。
次回はその処理を作成してスコアUIを完成させます。

# おまけ

UMG を C++ から 配置、操作する方法を書きましたが、下記の方法も使うことが出来るようです。

## BindWidget

UUW_Scoreクラスで、テキスト表示のポインタを取得するのに下記の様なコードを書きましたが

```C++:UUW_Score.cpp

void UUW_Score::NativeOnInitialized()
{
    :
    m_pTextBlock = Cast<UTextBlock>(GetWidgetFromName("TextBlock_0"));
}
```

.h で下記のようにしておくと、この GetWidgetFromName のくだりは不要に出来るようです。

```C++:UUW_Score.h
	UPROPERTY(EditAnywhere, meta = (BindWidget))
    class UTextBlock* TextBlock_0 = nullptr;
```

* .h で m_pTextBlock の代わりに下記の変数を作成
  * 変数名は WBP でテキストにつけた名前 **TextBlock_0** を使う必要がある
* .cpp で初期化せずとも TextBlock_0 を使う事ができる


### 参考リンク
この方法については下記から知る事ができました。

https://qiita.com/unknown_ds/items/4189b6c31322f46afdb3#comment-d5640cf44ed4f1a81813

https://dokuro.moe/ue4-cpp-translation-of-child-widget-without-blueprint/


## LoadSynchronous

WBP を配置する Actor に WBP を渡すのに Details からセットしましたが
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/049f4b90-1414-ed05-ef20-6e6eea3dbe54.png)

下記のように .cpp からもセットできるようです

```C++
void AAUWScoreController::BeginPlay()
{
	Super::BeginPlay();

    //>>> 追加
    FString path = "/Game/WBP_Score.WBP_Score_C";
    TSubclassOf<class UUserWidget> WidgetClass;
    WidgetClass = TSoftClassPtr<UUserWidget>(FSoftObjectPath(path)).LoadSynchronous();	
    //<<< 追加

	m_pUWScore = CreateWidget<UUW_Score>(GetWorld(), WidgetClass);
	ensure(m_pUWScore);
	if (m_pUWScore)
	{
		m_pUWScore->AddToViewport();
	}
}
```

* Content に置いた WBP_Score を読み込む
  * /Content の代わりに **/Game** を使う
  * WBP_Score という名前にしたので **WBP_Score.WBP_Score_C** を使う


### 参考リンク
この方法については下記の記事から知る事ができました。

https://qiita.com/EGJ-Kaz_Okada/items/8c7506e1a9f40f2b6d17

https://qiita.com/Kuroyanagi96/items/7849dfaed83fc8df5741

https://qiita.com/4_mio_11/items/00de94dbb6b6ed59176f