ネットワークマルチプレイヤーでキャラクタースキンの変更を行う実験レポート

この記事は[Unreal Engine 4 (UE4) Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ue4)の8日目の記事です

深刻なネタ切れにつき、マルチプレイヤー周りの内容でお茶を濁す魂胆であります

---

UnrealEngine4のマルチプレイヤーフレームワークは素晴らしいです。
非常に合理的な設計がなされており、基本ルールさえ把握すれば、比較的簡単にオンラインマルチプレイヤーゲームが作成できます

ただ、ゲームフレームワークの基本ルールを覚えるのはちょっとハードルが高いかもしれません
今回はその基本ルールの理解の助けになりつつ、ちゃんとネタっぽい内容を披露できればと思います


# ゲームフレームワークの基本

ドキュメント：[ブループリントのマルチプレイヤー機能](http://api.unrealengine.com/JPN/Gameplay/Networking/Blueprints/index.html)

簡単なおさらいをば。
UnrealEngine4のゲームフレームワークを構成する部品として、主なものは以下のとおりです

- GameMode
- GameState
- PlayerController
- PlayerState
- Pawn
- GameInstance

このうち、オンラインマルチプレイヤーを作るにあたって、Server側を主として保有するものは、

- GameMode
- GameState
- PlayerController
- PlayerState
- Pawn

となります（ほぼ全部じゃん）
特にGameModeに関しては、Clientからアクセスすることは出来ません（実体がないので）（わりと重要）
なのでClient側コードで`GetGameMode`ノードを引っ張ろうとしても何も入ってません

GameInstanceのみハブられていますが、これは各インスタンスごとに必ず1つ保有しています
Client側でも独立してアクセスできるため、わりと重宝します

それから、Pawnの生成、破棄はServer側のコードで行う必要があります（これも重要）
特に同期をとる必要があるものに関してはServer側で生成し、管理をすると覚えましょう

別段同期が必要ではない要素（エフェクト、サウンド、銃弾等のProjectileなど）に関しては、Client各自で生成します


# 今回やったこと

今回チャレンジしたことは下記の通り。

- 同一ブループリントを使用し、SkeletalMeshとAnimInstanceを動的に差し替える
- 上記をオンライン上で同期する

## 動作サンプル

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">アドカレ用に録画したサンプル <a href="https://t.co/swb2mJf9cY">pic.twitter.com/swb2mJf9cY</a></p>&mdash; 墨崎達哉 (@T_Sumisaki) <a href="https://twitter.com/T_Sumisaki/status/1071169532100673537?ref_src=twsrc%5Etfw">2018年12月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



## 解説

### マルチプレイヤー部分
マルチプレイヤー部分の大本はEpicGamesの[MultiplayerShootout](https://docs.unrealengine.com/en-us/Resources/Showcases/BlueprintMultiplayer)サンプルに準拠します
今回はGameState、PlayerStateには触りません

### Character
ThirdPersonCharacterを継承し、必要な要素を追加しています
今回はPlayerPawnNameという変数を追加し、`ExposeOnSpawn`、`Replicated`に設定しました
![2018-12-08_07h36_09.png](https://qiita-image-store.s3.amazonaws.com/0/117589/d629784f-c0c6-b6e4-90e0-d550b4cfc65f.png)

処理はBeginPlay時にPlayerPawnNameによってMeshとAnimInstanceを差し替えるだけです
全クライアントで統一して変える必要があるので、MultiCast指定です
![2018-12-08_07h40_39.png](https://qiita-image-store.s3.amazonaws.com/0/117589/515270ff-245a-c924-6baf-5d2fdea0e871.png)

### GameMode
GameModeではプレイヤーの参加処理とCharacterのSpawnを行います
新しいプレイヤーがゲームに参加するたび、GameModeのOnPostLoginイベントがコールされます
そこから各クライアント側での初期化を行い、その後サーバ側でCharacterをSpawnさせて紐づけます
このあたりはMultiPlayerShootoutとほぼ同じですね
![2018-12-08_07h44_58.png](https://qiita-image-store.s3.amazonaws.com/0/117589/957a7c69-deaf-43ac-467f-b9565354e704.png)

### PlayerController
PlayerControllerはClient側の前処理とServer側の前処理に別れています
その理由としては`GameModeにアクセスできるのはServer側のコードだけ`であるからです
前処理が終わった後、GameModeに処理を返さないといけないため、このような形になっています

ここで一つ工夫しているのは、GameInstanceから情報を取得している部分です
GameInstanceはレベル間をまたいで情報を保持することができるため、タイトル画面で選択したキャラクターの情報を保持するために使用しました
![2018-12-08_07h50_12.png](https://qiita-image-store.s3.amazonaws.com/0/117589/e891b233-b673-3377-3558-bea2ceb65e81.png)

なお、Client-Server間の情報の受け渡しは、イベント引数として行っています
PlayerController経由のPropertyReplicationでも情報の受け渡しは可能ですが、Replicationを使用しない理由は以下です

- 情報伝達の同期をとるのが難しい
- 頻繁に変化する情報ではない

「この関数の呼び出し時にこの情報は確実にほしいんだ」という場合については、Replicationではなく、イベントの引数として渡したほうがコントロールがしやすいです

### その他

イベントをReplicatesする際、確実にCallしたいイベントに関してはReliableフラグをONにしておきましょう
これがないと実行が保証されないので場合によっては処理が抜けることがあります


## まとめ
実用する際に改善すべき点はいくつかあります

- AnimInstanceまで変更しなければならない構成は避けるべき
    - Skeletonの構造まで変更が入るならそれは立派な別キャラクターなので分けたほうが無難
    - AnimInstanceを共有できる同一構造のSkeletalMeshであれば差し替えるのはアリ
- 構造体も利用したスマートな作り方に変更する
- スキンの格納方法の検証
    - 今回はサンプルなのでCharacter内部に情報をもたせているが、できれば外に出したい
    - MODやDLCも考慮した構造を考える必要がある

ただ、MeshやMaterialを差し替えたい程度であれば、わりと少ない作業量で実現できると思います

## メモ

- このサンプルプロジェクトを公開したかったが、軽い気持ちで使ったParagonアセットが超絶重いので今の所断念しています
- Paragonアセットのおかげでパッケージビルドが一晩かかりました