[UE4/UE5]ログインからログアウトまで！ネットワークゲームのフローを作る！

## はじめに
この記事では"UnrealEngine4"でのネットワークゲーム開発における「セッションの作成から破棄まで」を取り扱います。
本記事の段階でUE5がリリースされています。本記事の内容はUE5で利用できる部分もございますが、参考程度にご利用ください。

__サンプルプロジェクトの配布リンクは記事末尾にあります__

## 注意
本記事が初投稿になります。
至らない部分もありますが、お手柔らかにお願いします。

:::note info
タイトルを変更しました
旧タイトル「[UE4]ログインからログアウトまで！オンラインゲームのフローを作る！」
:::

## 前提
* UnrealEngine4.27.2を使用しています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/ad406e6a-04a9-c1bb-50de-aaccb4eaec9c.png)
* プラグインなどは一切使用していません
* C++を追記したり改変したりしていません
* 本記事では変数やアクターの同期(レプリケート)などのネットワークゲーム開発のインゲームに関する内容は扱いません
* 本記事はlistenサーバーを前提とした設計となります

## 結果|この記事で得られるもの
 ネットワークゲームを開発する際に必要となる「セッションの作成から破棄までの流れ」がわかります。

本記事の最後には、サンプルプロジェクトの配布リンクがあります

## 本題


### 1.プロジェクト準備
プレイヤーキャラクターやレベルがないと確認がやりづらいので「ThirdPersonTemplate」をおすすめします。
今回はC++を利用しないのでBlueprintのプロジェクトを作成します。

### 2.タイトル用のレベル「L_TitleMap」を作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/2cb45bcb-c856-35ef-fed6-08aa34b72352.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/216d2524-40eb-b8cc-9144-2ace550cfd14.png)
作成するレベルは「空のレベル」で問題ないです。

### 3.タイトル用UIになる「WBP_Title」を作成する
タイトル用のWidgetを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/58610466-cee4-ce1c-58f0-9a4632543049.png)
#### デザイン(WBP_Title)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/436ccd5e-ac75-c5a3-8853-98be22f3ac3c.png)
タイトルテキストとボタンが３つあればどんなデザインでも問題ありません。
各ボタンには、グラフで操作をする際にわかりやすいように、名前を付けるのがベストです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/6e9614f3-a1a5-4f4e-c6a9-2246e3777f4b.png)
* Btn_CreateSession
セッションの作成（ホストになって部屋を立てる人）のボタンです
* Btn_FindSession
セッションの検索画面（部屋を探す人）を開くボタンです
* Btn_QuitGame
ゲームを終了するボタンです
と命名しました

### 4.セッション作成画面「WBP_CreateSession」を作成する
セッション作成を行うための設定画面を作成します
※プレイ人数やLAN/WANの接続方法の切り替えを行える画面になるため、仕様によっては必要ありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/a9de6504-11f2-d4ff-6006-2eaa5082937e.png)
#### デザイン(WBP_CreateSession)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/37c17fea-df28-53a6-4d85-2e4a00114cd5.png)
セッションを作成するホスト側の設定項目になります。
参加できる最大人数と接続方法を設定できるようにボタンを配置します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/6b07dc98-5e6c-6551-c949-2112b923f796.png)
#### グラフ(WBP_CreateSession)
グラフの全体です。画像のようにノードを配置していきます。
各ボタンのOnClickedイベントに対して記述していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/4abbfe48-0bf0-6b1f-bd29-130b85b9b3ca.png)
ボタンのイベントは、変数でイベント出したいボタンをクリックして画面下に出てくるOnClickedの+マークを押すことで出すことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/e26b4dca-eede-a161-5c90-bba27bda5b56.png)
##### セッションの作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/665621c7-7fe9-a048-a26e-42de87aa3e89.png)
『Create Session』を実行することで、ホストとして部屋（Session)を作成することができます。"Public Connections"には参加できる最大人数を設定しますが、デザインしたように数を変動できるようにしたいので変数化したものを入れておきます。"Use LAN"はLANかWANかを選択でき、Trueの状態ではLAN回線でアクセスを行います。家庭内や職場など限られた環境内であればLANがTrueの状態で問題ありません。
具体的にはSteam Subsystemなどを利用する場合にはfalseで使用することがあると思います。
https://docs.unrealengine.com/4.27/ja/ProgrammingAndScripting/Online/Steam/

セッションの作成に成功した場合は、レベルを開きます。
OpenLevelノードで実行をしますが、今回はoptionsに「listen」と記述します。
レベルを開いた段階でホストとして認識され、後述するFind Sessionでの検索に引っかかるようになります。
開くレベルは特に特別なものである必要はなく「ThirdPersonExampleMap」で問題ありません。
今回は「ThirdPersonExampleMap」をコピーした「L_GameMap」を開くようにしています。

##### タイトル画面への遷移
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/30144f07-786b-4465-9661-53fbe222ee2d.png)
UXとしてタイトルに戻れるほうがいいので、タイトルWidgetを開いて、この画面(WBP_CreateSession)を閉じるように記述しておきます。

##### LANとWANの切り替え
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/42bed5b4-c76b-6987-663e-78733cf42b17.png)
左右矢印（> / <)をクリックした際に、LANとWANが切り替わるように先ほど作成した「Use LAN?」の変数を切り替えられるようにしておきます

##### 最大人数の設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/9c631614-30d3-f774-e6cb-cd2e442bc128.png)
こちらも矢印を押したときに人数を変えられるようにするのですが、今回は関数化しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/eac2b0f7-c89d-3d93-dcd0-f33fad88e7fc.png)
挙動としてこちらが設定した最大人数を超えてほしくなかったのでClampでの制御にしています。

#### デザイン(WBP_CreateSession)
最後にデザインに戻って、動的に変更したいテキストに変数を割り当てていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/c7085ec1-7329-ffff-0065-e658aee7e770.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/f93df2fe-18c3-3489-7c82-28f76cf9621f.png)
👆画像のように、コンテンツ＞Text の右にある[バインド]から紐づけていくのですが、一部の変数(int型やText型)は関数を作成せずに、変数を直接指定できるので便利です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/2b09f914-d89b-fc04-e43e-60a04aed8a19.png)
LAN/WANの切り替えは、Bool型でやっているので関数が必要になります。[バインディングを作成]から関数を作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/fec8e908-98b6-1f9e-1384-b2da882a27e1.png)

これで「WBP_Create Session」は終了です。

### 5.タイトル画面から作成画面に遷移できるようにする
#### グラフ(WBP_Title)
「WBP_Title」に戻り、”セッションを作る”を押したときに、「WBP_CreateSession」が開くようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/6da26a09-c5d2-f98c-62b0-737fd77de25d.png)

### 6.タイトルUIを表示する
L_TitleMapが起動した際にタイトルUIを表示させます。
レベルブループリントに記述しても問題はありませんが、今回はゲームモードに記述しています。
タイトル用のゲームモード「GM_Title」を作成して、Event BeginPlayにタイトルUIを表示する処理を記述します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/0df84147-9623-7f42-13a8-67abb47bf24b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/f539a849-186f-7746-f647-77f5fc874033.png)
L_TitleMapのワールドセッティングで設定しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/c1e009e6-6b3a-08f4-5201-93cf55e0fb89.png)


### 7.確認
「L_TitleMap」でプレイし、正常にレベルが開くかを確認しておきます
![CreateSession.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/34497ef1-9a14-1901-9380-fcf7c196d24f.gif)

### 8.セッションを探す画面を作成する
セッションを探すための画面を作成するのですが、各セッションをリストとして表示したいので、
画面となる「WBP_FindSession」とリストの項目となる「WBP_SessionData」の２つのWidgetを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/b25e6477-19ec-c5d3-186b-7131efb35af7.png)
#### デザイン(WBP_FindSession)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/a6964c92-5582-1b3f-0ec9-cef27ea00eb7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/3f02c0e0-0196-d252-c426-e82f5bf05a81.png)
必要なものは、
* 更新ボタン
* 検索するセッションの数が指定できるところ
* LAN/WANの検索の切り替え
* リストを出すことができるもの（今回はVerticalBoxを使用）
* 戻るボタン
になります
検索中にロード画面を出したいので、CicularThrobberを画面の中央に置いておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/d720ae4c-f862-19e8-49e0-2d3964b4da0f.png)

#### デザイン(WBP_SessionData)
VerticalBoxに取り付ける用のWidgetになります。
セッションの内容を表示して、ボタンを押すことで参加ができるという流れにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/b2fe2143-f2f5-04da-322f-b479c4c968b2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/20746862-0710-53d7-9dd9-2cc7737cea88.png)
非常にシンプルなもので、
* セッションを立てた人の名前
* 現在の参加人数
* セッションに参加できる最大人数
* 現在のPing
* 参加ボタン
をHorizontalBoxで横並びにしています。

#### グラフ(WBP_SessionData)
WBP_FindSessionで取得したセッションのデータをこのWidgetで受け取り、その内容を表示するだけになります。
セッションの情報は"Blueprint Session Result型"に格納されますので、変数を作っておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/d5fef445-729a-f2c5-49b9-01a95f35c6bf.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/8c396bb9-19f5-df86-f66b-84665654097f.png)
「インスタンス編集可能」と「スポーン時に公開」にチェックを入れておきましょう。
こうすることでCreate Widgetから作成するタイミングで変数に値をセットすることができて便利です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/c5450fc8-6984-f3fe-ee6e-45230519148d.png)
それぞれのテキストに対して値を入れていきましょう。
作成したSessionの変数から現在のセッションの状態が取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/0b27fba2-8f8a-1efb-8bb5-ae0b09cdf57d.png)

セッションへの参加は「Join Session」ノードを使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/78a97285-e2c2-aa66-020c-a21874f8b2c2.png)
参加する方（クライアント）はOn Successになった時点で自動的にホストのレベルへ遷移するため、OpenLevelは不要になります。

これで参加するロジックができたので検索のほうを作っていきます。

#### グラフ(WBP_FindSession)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/f99cb6ae-ded7-641a-b128-dac27f27b31b.png)

##### セッションの検索
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/74b1d556-2768-2cb6-0593-ec17ea092477.png)
「更新」ボタンが押されたときに、まずはVerticalBoxを初期化します。
その後、FindSessionノードを利用して、生成されているセッションを取得します。
MaxResultで検索する数を指定することができます。
100などの大きな数字を入れることができますが、数字を大きくすると検索時間が長くなってしまうため、テスト規模なら３～１０くらいで十分です。

動的に変更ができるように、MaxResultとUseLANは変数にしておきます。

また、デザインで配置したVerticalBoxやTextBoxなどはデザインの"詳細"にある「Is Variable」のチェックを入れることで変数としてグラフで変更を加えることが可能です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/6dc9d279-a9e5-c312-af2e-38525cecaf58.png)

FindSessionの結果である"Result"から「ForEachLoop」ノードにつなげていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/9e67b0ea-f5ff-ea37-1aa5-adc681b00a40.png)
FindSessionからは **「実行タイミング」「成功時」「失敗時」** の実行ピンがでているので、それぞれを繋げています。
一番上の実行タイミングでは、ロード中の表現として「Circular Throbber(CT_Loading)」を表示し、[更新]ボタンや検索数を変更できるボタンを停止するためにSet Is Enabledを繋げています。一番下の失敗タイミングはその逆になります。
真ん中の成功タイミングではCreate Widgetから「WBP_SessionData」を生成し、VerticalBoxにAdd Childでリストとして登録します。Widgetの生成時に、検索結果のSessionをCreate Widgetのピンにさしておきます。

##### タイトル画面への遷移
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/32061e08-7117-bb25-7491-3f80d2508241.png)
UXとしてタイトルに戻れるほうがいいので、タイトルWidgetを開いて、この画面(WBP_FindSession)を閉じるように記述しておきます。

##### チェックボックスでのLAN/WAN切り替え
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/8cc1603a-2f6c-1948-8f7b-4f1f8d301e21.png)
デザインで配置したCheckBoxからはシンプルなイベントが発行できますので、Use LANの変数に入れておきます。

##### 検索数の設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/37e79fac-1013-325b-18b1-20f10cd70750.png)
矢印ボタンを押したときに検索数を変更できるように設定しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/db64cad8-a147-99aa-a6b4-0f53d6a3febd.png)
関数の内容は、WBP_CreateSessionで行った内容と同じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/420f4d70-82e7-e789-ab86-71783d5c573c.png)
検索数を表示しているテキストと変数を紐づけておきます。

### 9.確認
ここからは２人以上のプレイヤーがオンラインで接続している想定で確認作業を行います。
L_TitleMapでプレイをする前に設定をしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/526c134c-247f-c545-82a8-596182ae3baf.png)
プレイ人数は想定している人数を入れ、ネットモードはPlay Standaloneにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/5bbec3b6-4b9c-4636-1d05-227b4e4b481c.png)
 **ネットモードの「Play As Listen Server」はセッション作成/参加を返さずにプレイ確認をする際に設定** します。
※L_GameMapを開いて直接プレイをする場合には設定してください。
![FindSession.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/4861690e-e948-e6c7-f18a-1fd5a8bea5cc.gif)
上のgifのように同じレベルでプレイできていることが確認できれば成功です。

### 10.めんどくさいところをやっていく
ここまでの工程は先駆者様の記事などが出てきて作りやすいのですが、ログアウトやそれに伴うエラー処理などを自分で考えて作っていかなければなりません。
 **あくまでもサンプル** ではありますが、デフォルトのUEのノードだけで可能な限りエラーに対処できそうな流れを作っていきます。

### 11.ログアウトを確認するための準備
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/13b82627-6163-12a0-4f5f-f7c23c799516.png)
プレイヤーがUIからログアウトができる画面（ゲームメニュー画面）を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/62b0bbdb-0ef1-9f2d-fa90-2c5d679d4ea2.png)
インゲームの確認をするために必要なクラスを作成しておきます。
* BP_Player
ThirdPersonCharacterをコピーしたものです。
* CP_PlayerController
PlayerControllerクラスを継承して作成します。オンラインゲームでは設計上の観点からPlayerControllerをしっかりと利用していくほうが後々楽になります。
* BPI_GameController
CP_PlayerControllerにアクセスするためのインターフェースです。

作成後、「GM_Sample(インゲーム用のゲームモード)」を作成し、BP_PlayerとCP_GameControllerを設定しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/a55894a9-c737-76a1-ce26-92f5b729c19d.png)

### 12.ゲームメニューUI「WBP_GameMenu」を作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/1a851786-5cc6-6c43-21e0-c604b8a3fe56.png)
#### デザイン(WBP_GameMenu)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/61e78b16-bc27-5190-7666-3c38363b30ee.png)
今回はボタンが２つあればどんなものでも問題ありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/1696c62c-594f-af71-2ef2-81a769e50806.png)
#### グラフ(WBP_GameMenu)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/c3974bc6-fa03-a8f7-e9a1-aea7e59ac0c4.png)
このUIではWidget内に具体的な処理を記述せずに、"イベントディスパッチャー"を作成して呼び出すだけにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/25963141-4acc-0ce7-dec2-9e1b621dc50d.png)
また、クラスのデフォルト＞詳細 から Is Focusableにチェックを入れておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/c1515d90-8a34-c6df-ff75-ac6f05b01967.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/67d83000-9352-db69-3e96-e6126326a8f9.png)

### 13.ゲームメニューUIからログアウトできるようにする
#### BPI_GameControllerを編集
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/bc1d6eae-cd8d-65ca-5431-bed0af1404ad.png)
インターフェースとして"ShowGameMenuUI"関数を作成しておきます。

#### BP_Playerを編集
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/df5e7eb5-ac6c-06aa-88b0-7d65c7da74f1.png)
好きなボタンのインプットイベントから先ほど作成した"ShowGameMenuUI"を呼びだすように接続します。

#### CP_GameControllerを編集
全体図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/0d4cf3ad-3907-3bc9-700b-c64cefcde389.png)
BeginPlayに"Set Input Mode Game Only"を接続しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/2abd9aed-bd1f-444b-0ab6-e14aeee86afb.png)
カスタムイベントを追加して、"Logout"と命名しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/dd13f881-4578-9b6a-1833-fab567bf4fad.png)
DestroySessionは、サーバー（セッションを立てたホスト）が実行するとセッションを破棄することができます。
クライアントの場合は、セッションに参加しているという情報そのものを破棄します。
DestorySession自体が俗にいうログアウトということではなく、レベル遷移を伴うことでゲームから離脱することができます。
ゲームに離脱せずにホストがDestorySessionのみを行った場合は、FindSessionの検索に引っかからないようになるため、特定のタイミングで呼ぶことで、公開していた部屋を閉じて途中参加をさせない、といったような処理にできます。
DestorySessionの成功時にOpenLevelでタイトルレベルに戻るように設定しておきます。

先ほど作成したインターフェース関数"ShowGameMenuUI"からゲームメニューWidgetの生成と表示を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/27197667-926c-bfbc-1ae8-3ef56965a6ab.png)
Widgetを多重に生成したり、表示したりしないような制御をしています。

その後ろには、Widget内のボタンが押されたときの挙動としてイベントディスパッチャーを紐づけておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/6969704a-40f6-2953-f155-6b0e39eeabe1.png)
後述するCloseGameMenuUIもカスタムイベントから作成して、紐づけておきます。

ゲームメニューを閉じる処理（CloseGameMenuUI）を記述します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/d1dfe705-f98e-30f9-b310-f16df3a52730.png)

### 14.確認
**セッションの作成→レベル遷移→セッション破棄（ログアウト）→セッションの作成/検索** が正常に行えるかを確認します。
![LogoutDefault.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/2b1e77ce-daf0-4f38-2c13-082f7772c190.gif)

### 15.【問題解決】切断された側がセッションの作成も参加もできない問題
Listenサーバーでは、セッションに参加しているときに、ホストがログアウトした場合、クライアントは直ちにゲームが終了されます。
UEでもそのようになっており、ホストとの接続状況が悪くなった（ホストがログアウトした）場合、クライアントは『プロジェクト設定 > マップ＆モード > ゲームのデフォルトマップ』に設定したレベルに遷移します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/32a67cf5-adcf-dcd1-3b79-52dbf86dcb53.png)

この際、セッションがなくなって強制的に離脱することになったクライアントは「まだ」セッションに参加した履歴のようなものが残っており、セッションを作成したり、セッションに参加したりすることができなくなってしまいます。

この問題の解決は比較的簡単で「L_TitleMap」に遷移する度にセッションを破棄することで解決します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/e76c71a6-2e6f-2558-9135-1b9d7c987a9b.png)
「GM_Title」のEventBeginPlayにDestorySessionを記述しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/9509daef-61fd-d17a-adfe-eecd46a5b4b2.png)

これでこの問題は解決できますが、初めてゲームを起動した際にもDestorySessionが実行され、必ずOnFailureになってしまいます。実害はないのですが、設計上美しくないなぁと思うので、**ネットワークエラーが発生したときのみ、DestroySessionを実行するという形**にします。

#### GameInstanceを作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/eb4d741c-7ea8-bc0a-fd73-5d7b33b0207a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/2348d386-89d3-62f2-8ca5-aa61e35d0c7f.png)
ネットワークエラーに関するイベントはGameInstanceで実行されているため、GameInstanceを継承した「GI_OnlineGameSample」を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/d719019b-c956-e3d4-077f-e4975c5c9c0f.png)
プロジェクト設定から作成したGameInstanceを適用しておきます。

GameInstanceでは「ネットワークに問題が発生したときに発行されるイベント」と「レベルを移動した際に問題が発生したときに発行されるイベント」の２つがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/1b05b947-836d-48ef-ffba-b9fdf8610999.png)
それらからは「原因」となる内容が取得できるため、エラーの内容によって実行する内容を変えることもできます。

今回は、「大雑把なエラー」を取得するため両方のイベントに「Error Occurred」という自作したBool型の変数を挿入しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/b1e79158-8815-a6ef-5a2e-24d66228d41a.png)

#### ダイアログUIを作る
プレイヤーからするとなぜゲームがいきなり落ちてしまったのかわからないので、エラーで落ちた際には「エラー」であることを伝えてあげます。
「WBP_Dialog」を作成します。ダイアログは様々な場面で使えるため、汎用的なものにしておきます。

##### デザイン(WBP_Dialog)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/c3c0dadc-ff0c-4c7c-81fd-49fb066b42a2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/c2ffcdd0-6898-5696-59d2-86f176473e2d.png)
##### グラフ(WBP_Dialog)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/156a8af2-0e24-1f38-caa0-d4a659b07441.png)
"Dialog Text"の変数はこのWidgetの生成時に変更したいので、「インスタンス編集可能」と「スポーン時に公開」にチェックを入れておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/8acb8a65-7886-56b1-a9f1-43ae71177043.png)

#### GM_Titleを編集
準備が整ったので、エラーが出た際にだけ、ダイアログを表示してセッションを破棄できるようにします。

GameInstanceの"Error Occurred"を取得して処理を分岐します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/fb3dee78-3c58-863b-2cd0-39398dccb9b7.png)
タイトルUIを生成するタイミングが"Error Occurred"のTrueとFalseで異なるため、「ShowTitleUI」というカスタムイベントを作成しています。

失敗時には「WBP_Dialog」を表示してセッションの破棄を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/6c92abdf-eb5c-69e9-7747-02cbb38bef6f.png)
破棄のタイミングはどこでも問題はないのですが、今回は「了解」のボタンを押したときのイベントに紐づける形で実装しました。ダイアログを出した後には"Error Occurred"はFalseにしておきます。

ダイアログに表示する文言(Dialog Text)は生成時に設定できるようにしているため、GameInstanceに作成した"Error Occurred"の変数と同じ要領でここでネットワークのエラー内容を表示することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/5a1f4ab3-9377-8785-5cae-6484cf3bc930.png)

### 16.確認
２人以上のプレイヤーがセッションに参加している状態で、ホストがログアウトした場合にクライアント側にダイアログが生成されます。また、クライアントは、そのまま継続してセッションの作成やセッションの検索が行える状態になります。
![Dialog.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/3f223084-3272-10eb-9363-88aa9e11e9b3.gif)

## さいごに
今回はUnrealEngineでのセッション作成から破棄までをどのように実装すればいいのだろうというところを書いてみました。
ゲームジャムなどの短期の開発なら問題ないかなと思っていますが、販売まで行く場合にはそのリリースするプラットフォームごとの制約などがあるため、様々な要件をクリアする必要があります。

中規模を超えるゲーム開発を考える際には、専用のエンジニアを立てるのはもちろん、有用なプラグインを利用していくことで、インゲームの開発により注力できるのではないかと思います。

### サンプルプロジェクト配布
本記事を最後まで進めた __UE4.27のプロジェクトは以下からダウンロードが可能__ です。
ゲームレベルにはサンプルアクターが置いてありますので、レプリケートの学習にも利用できると思います。

[ダウンロードはこちら(GoogleDrive)](https://drive.google.com/file/d/1xWHQSGENSW7HNEtgI_fy109yNmbQXxIf/view?usp=sharing)
![HighresScreenshot00000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588827/34ec74eb-7c83-1486-d5ab-23b37129da84.png)

:::note warn
学習目的での利用は一切問題ありません。
商用目的での利用も問題ありませんが、本記事以外の内容については一切サポート等を行いません。
:::