[UE4] ブロック崩しを作る #7 デリゲート(C++)

# はじめに
この記事は UE4 でブロック崩しを作るシリーズ の続きになります。
[前回](https://qiita.com/jamaican/items/034db7b561b2fe23ea22) は スコアを表示するUI を追加しました。
今回はそのスコア表示が加算される仕組みを UnrealC++ のデリゲート を使って実装します。

# 完成ゲーム
![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/46c253e1-e6af-8003-cd38-e51c087491fe.gif)

# 参考リンク
[【UE4 C++】デリゲートの使い方まとめ](https://dokuro.moe/ue4-cpp-how-to-use-delegate/)
[Unreal Engine 4メモ：デリゲートをprivateで書く](https://qiita.com/sy_/items/541fd18e460f5a881c75)

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

# 目次
[設計](#設計)
[実装](#実装)
[スコアを管理するクラス(GameSate)の作成](#スコアを管理するクラスgamesateの作成)
[スコア管理クラス(GameState)にデリゲートを追加](#スコア管理クラスgamestateにデリゲートを追加)
[スコア変化時にデリゲートに登録された関数を実行できるようにする](#スコア変化時にデリゲートに登録された関数を実行できるようにする)
[スコア再描画関数をデリゲートに追加](#スコア再描画関数をデリゲートに追加)
[ブロック破壊時にスコア管理にブロック破壊を通知](#ブロック破壊時にスコア管理にブロック破壊を通知)
[実行](#実行)
[補足](#補足)
[次回](#次回)

# 設計

## 1. シンプルな実装
実装の前に設計を考えます。
まずはシンプルに「ブロックが破壊される」→「スコアが加算される」→「スコア描画更新」のフローを考えてみます。

```plantuml
@startuml
participant ブロックActor
participant スコア描画

[o->> ブロックActor : NotifyHit()
ブロックActor -> ブロックActor : ブロック破壊
ブロックActor -> スコア描画 : スコア加算()
activate スコア描画
deactivate スコア描画
スコア描画 -> スコア描画 : スコア再描画
@enduml
```

#### 1. の問題点

* ①「ブロックActor」がスコア加算関数を呼んでいる
  * ブロックがスコア加算までしてしまうのではブロックの仕事を多少逸脱してしまいます。スコアの加算はゲームのルールなのでそれを管理するクラスを作成しそこに任せ、ブロックはブロックの仕事だけをさせた方が設計として良さそうです。
* ②「スコア描画」がスコアのデータを管理してしまっている
  * スコアの実際の値 (0, 10, 30等) をスコア描画が持っています。スコアの表示は例えばリザルト画面でも行うかもしれません。スコアは他のクラスからでも使い易いように別の場所に置かれている方が良さそうです。

### 2. スコアの ビュー層 と モデル層 を分割

②を解消するためスコアの数値を管理するモデル層を作成しビュー層と分離しました

```plantuml
@startuml
box view #Strategy
participant ブロックActor
participant スコア描画
end box

box model #Application
participant スコア管理
end box

[o->> ブロックActor : NotifyHit()
ブロックActor -> ブロックActor : ブロック破壊
ブロックActor -> スコア管理 : ブロック破壊を通知
スコア管理 -> スコア管理 : スコア加算
スコア管理 -> スコア描画 : スコア再描画
@enduml
```
#### 2. の問題点
* ①の問題が解消されていない
* ③スコア再描画をモデルから呼んでしまいモデルがビューを参照してしまっている
  * 独立していないといけないモデル層がビュー層に依存してしまっている

### 3. モデル層からビュー層を参照しないように
③を解消するためスコア再描画のフローを修正します。
一番始めに「スコア描画」から「スコア管理」にスコアが変化した時に呼び出して欲しい関数（スコア再描画関数）を登録します。
こうする事でモデル（スコア管理）がビュー（スコア描画）を参照する事なく、ブロック破壊等のスコア変化時にスコア再描画の関数を呼び出す事ができます。このような呼び出しは UE4 の**デリゲート**を使って実現させます。

```plantuml
@startuml
box view #Strategy
participant ブロックActor
participant スコア描画
end box

box model #Application
participant スコア管理
end box

スコア管理 <[#red]- スコア描画 : スコア変化時に\n呼び出して欲しい関数\nを登録
[o->> ブロックActor : NotifyHit()
ブロックActor -> ブロックActor : ブロック破壊
ブロックActor -> スコア管理 : ブロック破壊を通知
スコア管理 -> スコア管理 : スコア加算
スコア描画 -[#red]> スコア描画 : スコア再描画
@enduml
```
#### 3. の問題点
* ①の問題が解消されていない

### 4. コントローラー層を追加

①の問題を解消するためコントローラーを追加します。
ビューからのイベントをコントローラーで受け取り、コントローラーからモデルの関数を呼ぶようにします。

```plantuml
@startuml
box view #Strategy
participant ブロックActor
participant スコア描画
end box

box Controller #Technology
participant Controller
end box

box model #Application
participant スコア管理
end box

スコア管理 <- スコア描画 : スコア変化時に\n呼び出して欲しい関数\nを登録
[o->> ブロックActor : NotifyHit()
ブロックActor -> ブロックActor : ブロック破壊
ブロックActor -[#red]>> Controller : ブロック破壊を通知
Controller -[#red]> スコア管理 : スコア加算()
activate スコア管理
deactivate スコア管理
スコア描画 -> スコア描画 : スコア再描画
@enduml
```

# 実装
この設計に基づき実装を行います。
今回はデリゲートがメインのため簡易的な 3. で実装を行います。

## スコアを管理するクラス(GameSate)の作成

スコアを管理するクラスを追加します。
Unreal のフレームワークでは GameState という場所がよいそうです
(ネットワークを使ったゲームではないので恩恵はなさそうですが)
https://docs.unrealengine.com/4.27/ja/InteractiveExperiences/Framework/GameMode/

上部メニューから File → New C++ Class.. を選択
開いたウィンドウから「Game State Base」を選択して Next をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/0f7a5bc6-6e7a-dd52-dfd0-b60af0ada70c.png)

Name を「GameStateBase_BB」として Create Class をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/9835df8d-e941-c2e8-148b-b473b680201a.png)

Visual Studio が起動しプロジェクトに GameStateBase_BB.h と .cpp が追加されています。
World Settings を開いて Game State Class を GameStateBase_BB に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/ea3e5288-9df0-85e6-7889-2160c9499de3.png)


## スコア管理クラス(GameState)にデリゲートを追加
作成した GameStateBase_BB にデリゲートを追加します。
デリゲートにはいくつか種類がありますが、ここでは複数の関数をバインドできる**マルチキャスト**で引数を１つ指定できるようにします。
デリゲートの種類や実装方法についてはこちらのブログにとても詳しくまとめられています。
https://dokuro.moe/ue4-cpp-how-to-use-delegate/

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

#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "GameStateBase_BB.generated.h"

UCLASS()
class BREAK_BLOCK_API GameStateBase_BB: public AGameStateBase
{
	GENERATED_BODY()

public:
    // コンストラクタ
	GameStateBase_BB()
		: mScore(0)
	{
	}

protected:
	int mScore;

public:
    // マルチキャストを宣言、１つ引数を使えるように_OneParamをつける
	DECLARE_MULTICAST_DELEGATE_OneParam(FScoreUpdateDelegate, int);
    // デリゲートを作成
	FScoreUpdateDelegate ScoreUpdateDelegate;
};
```

## スコア変化時にデリゲートに登録された関数を実行できるようにする
GameStateBase_BB.h に下記の NotifyBreakBlock関数 を追加します。
これをブロック破壊時に呼んでもらう事で、スコアの加算と、デリゲートに登録された関数を実行が出来るようになります。

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

#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "GameStateBase_BB.generated.h"

UCLASS()
class BREAK_BLOCK_API GameStateBase_BB: public AGameStateBase
{
   :
public:
    // ブロック破壊時に呼んでもらう関数
	void NotifyBreakBlock()
	{
		// スコア加算
        mScore+=10; 
        // デリゲートに登録された関数を実行、加算されたスコアを渡す
		ScoreUpdateDelegate.Broadcast(mScore);
	}	
};
```


## スコア再描画関数をデリゲートに追加
スコア描画のBeginPlay時にスコア再描画関数をデリゲートに追加します。
これで GameStateBase_BB で ScoreUpdateDelegate.Broadcast() が呼ばれた時に m_pUWScore->UpdateScore() が呼ばれるようになります。

```C++:AUWScoreController.cpp
// Called when the game starts or when spawned
void AAUWScoreController::BeginPlay()
{
	:
    // デリゲートに追加
	auto gameState = GetWorld()->GetGameState<GameStateBase_BB>();
	gameState->ScoreUpdateDelegate.AddUObject(m_pUWScore, &UUW_Score::UpdateScore);
}
```

## ブロック破壊時にスコア管理にブロック破壊を通知
Actor から GameStateBase は下記のように呼び出せます。

```C++:BlockActor.cpp
void ABlockActor::NotifyHit(UPrimitiveComponent* MyComp, AActor* Other, UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)
{
	Super::NotifyHit(MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit);

	auto pGameState = GetWorld()->GetGameState<GameStateBase_BB>();
	pGameState->NotifyBreakBlock();

	K2_DestroyActor();
}
```

# 実行
ビルドをしてプレイするとブロックが破壊されたタイミングでスコアも加算されるようになりました。
![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39286/46c253e1-e6af-8003-cd38-e51c087491fe.gif)

# 補足
この記事では説明を簡略化するために、GameState のデリゲートをpublicにしてコードを書きましたが、本来はデリゲートは直接公開せず、デリゲートに関数を追加する関数だけを公開するのが好ましいです。その方法については下記の記事にとても詳しくまとまっています。

https://qiita.com/sy_/items/541fd18e460f5a881c75

# 次回
次回はゲームのマスターデータをCSVなどで作成し、それを UE4 の C++ で読み込む方法についてまとめる予定です。