マルチプレイヤー プログラミングのクイックスタートガイドをBPでやってみた

[Unreal Engine (UE) Advent Calendar 2023](https://qiita.com/advent-calendar/2023/ue)の9日目の記事になります．

## はじめに
UEで新しいことを学ぼうとする時に、まず最初に公式ドキュメントを読むことが多々あると思います。
今回はマルチプレイゲームを作りたいけどC++書くのはちょっと…という人のために、C++で書かれた公式ドキュメントの内容をBPだけでやってみました。
僕自身、勉強し始めたばかりなので不適切な点があるかもしれません。見つけた際はぜひご指摘いただけると幸いです。
レプリケートやRPC，所有権についての詳細は触れません．参考文献などをご参照ください．

## 環境
Windows10
UE5.3.2

## 目次
※公式ドキュメントの手順に沿って進めます
- [基本設定](#基本設定)
- [RepNotifiesを使用してプレイヤーのヘルスをレプリケートする](#repnotifiesを使用してプレイヤーのヘルスをレプリケートする)
- [プレイヤーをダメージに反応させる](#プレイヤーをダメージに反応させる)
- [レプリケーションで発射物を作成する](#レプリケーションで発射物を作成する)
- [発射物によるダメージを作成する](#発射物によるダメージを作成する)
- [発射物を発射する](#発射物を発射する)

## 基本設定
新規プロジェクトをサードパーソンテンプレートで作成します。
以下の点を確認してください。
- BPプロジェクトであること
- スターターコンテンツが含まれていること
- デスクトップをターゲットにしていること

![スクリーンショット 2023-12-10 124655.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/b1ee16c0-66c2-f8a6-a61b-a2069e88b70d.png)

プロジェクトを作成してエディタを開くことができたら、PlayerStartを複製しておきます。
![スクリーンショット 2023-12-10 125651.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/f4c91e56-de89-deaa-214d-19e5ea68e695.png)

次にエディタ起動設定を変更します。
![スクリーンショット 2023-12-10 130115.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/9327393d-e952-d767-8c43-87a6302bf951.png)


ここまで設定できたら一度起動してみましょう。「NewEditorWindow(PIE)」で起動するのがおすすめです．ウィンドウが2つ出て、それぞれキャラクターを操作できるようになっています。CharacterMovementコンポーネントで移動を制御しているだけで、キャラクターの位置が同期されています。

実行したあとMessageLogに以下のようなWarningが出るかもしれません．
![スクリーンショット 2023-12-10 131646.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/47683d11-98a0-f6ee-fcc3-25b22be76641.png)
その場合，「Editor Preferences→Level Editor→Play Credentials」を開きます．「Credentials」にプレイヤーの数だけ追加します．情報は適当で大丈夫です．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/61e9bfdd-82b1-2666-d5be-7d31504b270c.png)

## RepNotifiesを使用してプレイヤーのヘルスをレプリケートする
キャラクターのHPを設定していきます。
BP_ThirdPersonを開いてください。
最大HPを表す「MaxHealth」と、現在HPを表す「CurrentHealth」をfloat型で作成します。今回「MaxHealth」の初期値は100にしました．
![スクリーンショット 2023-12-10 133159.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/527798bb-4fd8-08ec-0904-69814789917c.png)

「CurrentHealth」は他プレイヤーと同期する必要があるのでレプリケートの設定をします。
![スクリーンショット 2023-12-10 133346.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/f04f897f-057f-3d20-c01f-52ac1c6369dc.png)

RepNotifyに設定すると「OnRep_(変数名)」という関数が自動的に作られます．変数の値が変更されたときに実行したい処理を書きます．
今回は残りのHPをPrintStringで表示する関数を用意し，ダメージを受けたときに実行するようにします．
「OnHealthUpdate」という関数を作成し，以下のように組みます．
![スクリーンショット 2023-12-10 152459.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/4d76c4c3-79a9-b345-e531-31cc402f3896.png)
![スクリーンショット 2023-12-10 152522.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/3661f090-60a5-25b1-9f63-7c3522b3e9fc.png)

「OnRep_CurrentHealth」で「OnHealthUpdate」を呼び出します．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/778c51a2-3cc2-62ee-c6e1-8a430e3c5d7e.png)

プレイヤーのHPを初期化するために，「BeginPlay」イベントで「SetCurrentHealth」を呼び出します．しかしこのまま呼び出してもクライアント側でしか実行されず，サーバーには反映されません．そのためサーバーで実行させるためのイベントを用意します．カスタムイベントを作成して「Replicates」を「Run on Server」にします．Reliableにチェックを入れることで，処理負荷などでRPCが送信されなかったときに再送信するようになります．
![スクリーンショット 2023-12-10 171552.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/2f89191d-fa39-a5ac-0c3c-e5c4f11d531c.png)
作成したイベントを「BeginPlay」で呼び出します．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/4eaa1157-484f-6ebb-2ef3-b745859fe0de.png)


## プレイヤーをダメージに反応させる
次にプレイヤーがダメージを受けた時の処理を書いていきます。
BP_ThirdPersonを開いてください。
HPのゲッターとセッターを作成します。
変数はprivateにして、外部からはゲッターとセッターを通じて
アクセスできるようにします。

![スクリーンショット 2023-12-10 133730.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/e39a12f2-af0c-22a7-d2a3-885ae4331ddd.png)

![スクリーンショット 2023-12-10 133730.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/6d609405-22fd-2e1f-f1d6-75b828e0236c.png)

セッターに関しては、サーバーだけが値の変更が可能であるようにしておくべきです。そのため「Switch Has Authority」ノードを使用して、関数の実行者がサーバーの時だけ処理を行うようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/c51cf7fc-1877-1020-3c31-8ccd5c270e1d.png)



次にプレイヤーへダメージを適用する処理です．ここでは「AnyDamage」ノードを使用します．ダメージを通知する「Apply Damage」ノードや「Any Damage」ノードはサーバーで実行されるようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/a6cfdf4b-0028-05d2-8be7-a1aa88ece163.png)



## レプリケーションで発射物を作成する
まず発射物のActorを作成します．名前は「BP_MultiPlayProjectile」としました．
[画像]
作成したBPを開いて，以下のコンポーネントを追加します．
- StaticMesh
- SphereCollision
- CascadeParticleSystem
- ProjectileMovement

![スクリーンショット 2023-12-10 153442.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/2cbc7391-8277-ca95-52e6-e543eaba53f4.png)

ParticleSystemの設定は以下のようになっています．StarterContentにある爆発エフェクトを使用するためにCascadeのParticleSystemを使用しています．発射物を生成したと同時にエフェクトが再生されないように，「AutoActivate」をfalseにしています．
![スクリーンショット 2023-12-10 153555.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/9e26d6cf-d417-b3ff-723c-f5c18920f569.png)

コンポーネントを追加したら，衝突時にプレイヤーへ与えるダメージ用の変数を作成します．
![スクリーンショット 2023-12-10 154649.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/160fc5db-9ba3-65c8-7717-65f684652767.png)

アクターをレプリケートするために，「ClassDefaults→Replication→Replicates」にチェックを入れます．
![スクリーンショット 2023-12-10 154817.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/0697a042-c18d-6ec9-c158-12f0b478277c.png)

「ProjectileMovement」コンポーネントを以下のように設定します．
![スクリーンショット 2023-12-10 155022.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/055dd51f-9fbb-d5e7-3853-de325ed790ce.png)




## 発射物によるダメージを作成する
BP_MultiPlayProjectileのEventGraphを開いてください．
発射物がプレイヤーに衝突したらダメージを与えるようにします．今回は「OnComponentBeginOverlap」を使用します．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/29168156-dde0-00ce-a29e-393a45623f6a.png)


アクターを破棄する際にエフェクトを再生します．SEなどを追加しても良いです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/f331289d-1cb6-0629-62f3-fe1b060f016b.png)


## 発射物を発射する
特定のキーを押したら発射物を発射するようにします．せっかくなのでEnhanced Inputを使用してみます．まずInputActionを新たに作成します．
![スクリーンショット 2023-12-10 160150.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/73d0cf59-394d-09dc-4832-8da500bb07d0.png)

Triggersの要素を追加して，Pressedを設定します
![スクリーンショット 2023-12-10 160319.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/303ed8ce-9037-29ff-79cc-c7215b9d8f3f.png)

「Content→ThirdPerson→Input→IMC_Default」を開き，先ほど作成した「IA_Fire」を登録します．今回はFキーを押したら発射するようにします．
![IMC.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/0146a899-7e40-aae5-7bf0-62d32493a317.png)

ここまでできたらBP_ThirdPersonを開いて処理を書いていきます．
次発射できるようになるまでの時間と，発射中のフラグの2つの変数を追加します．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/64f05b5c-f299-163e-df44-4827e95e073b.png)

発射用のイベントを用意します．発射物を生成するイベントだけ「Run on Server」に設定します．サーバー上で生成することで，他クライアントにレプリケートされます．
![スクリーンショット 2023-12-10 162034.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/3d5da13c-2e56-dd42-42d8-014e87858f03.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/80ea3391-47ad-fc9a-91a0-182c00b507de.png)

Fキーを押したときに「StartFire」イベントが呼び出されるようにします．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256220/81440980-7be9-c04d-bc6b-cd10d74ae9de.png)

これで完成です．実行してみましょう．
<iframe width="560" height="315" src="https://www.youtube.com/embed/v13-8ctRcEY?si=P8ZmnaSy92c2tI_a" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

## おわりに
最後まで読んでいただきありがとうございました！
ざっくりとした説明だったので，補足必要なところや不備があればコメントいただけると幸いです．
またこういう風にした方がいいなど改善案も受け付けています．

## 参考文献
https://docs.unrealengine.com/latest/ja/multiplayer-programming-quick-start-for-unreal-engine/

https://docs.unrealengine.com/latest/ja/networking-overview-for-unreal-engine/

※↓UE4.27のページですが内容は問題ありません

https://docs.unrealengine.com/4.27/ja/Resources/ContentExamples/Networking/



※↓[こちら](https://ue5study.com/unrealengine-online/)にドクセルのリンクもあります

https://historia.co.jp/archives/6750/