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

この記事はUnreal Engine 4 (UE4) Advent Calendar 2018の8日目の記事です

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


UnrealEngine4のマルチプレイヤーフレームワークは素晴らしいです。

非常に合理的な設計がなされており、基本ルールさえ把握すれば、比較的簡単にオンラインマルチプレイヤーゲームが作成できます

ただ、ゲームフレームワークの基本ルールを覚えるのはちょっとハードルが高いかもしれません

今回はその基本ルールの理解の助けになりつつ、ちゃんとネタっぽい内容を披露できればと思います


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

ドキュメント:ブループリントのマルチプレイヤー機能

簡単なおさらいをば。

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を動的に差し替える

  • 上記をオンライン上で同期する


動作サンプル


解説


マルチプレイヤー部分

マルチプレイヤー部分の大本はEpicGamesのMultiplayerShootoutサンプルに準拠します

今回はGameState、PlayerStateには触りません


Character

ThirdPersonCharacterを継承し、必要な要素を追加しています

今回はPlayerPawnNameという変数を追加し、ExposeOnSpawnReplicatedに設定しました

2018-12-08_07h36_09.png

処理はBeginPlay時にPlayerPawnNameによってMeshとAnimInstanceを差し替えるだけです

全クライアントで統一して変える必要があるので、MultiCast指定です

2018-12-08_07h40_39.png


GameMode

GameModeではプレイヤーの参加処理とCharacterのSpawnを行います

新しいプレイヤーがゲームに参加するたび、GameModeのOnPostLoginイベントがコールされます

そこから各クライアント側での初期化を行い、その後サーバ側でCharacterをSpawnさせて紐づけます

このあたりはMultiPlayerShootoutとほぼ同じですね

2018-12-08_07h44_58.png


PlayerController

PlayerControllerはClient側の前処理とServer側の前処理に別れています

その理由としてはGameModeにアクセスできるのはServer側のコードだけであるからです

前処理が終わった後、GameModeに処理を返さないといけないため、このような形になっています

ここで一つ工夫しているのは、GameInstanceから情報を取得している部分です

GameInstanceはレベル間をまたいで情報を保持することができるため、タイトル画面で選択したキャラクターの情報を保持するために使用しました

2018-12-08_07h50_12.png

なお、Client-Server間の情報の受け渡しは、イベント引数として行っています

PlayerController経由のPropertyReplicationでも情報の受け渡しは可能ですが、Replicationを使用しない理由は以下です


  • 情報伝達の同期をとるのが難しい

  • 頻繁に変化する情報ではない

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


その他

イベントをReplicatesする際、確実にCallしたいイベントに関してはReliableフラグをONにしておきましょう

これがないと実行が保証されないので場合によっては処理が抜けることがあります


まとめ

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


  • AnimInstanceまで変更しなければならない構成は避けるべき


    • Skeletonの構造まで変更が入るならそれは立派な別キャラクターなので分けたほうが無難

    • AnimInstanceを共有できる同一構造のSkeletalMeshであれば差し替えるのはアリ



  • 構造体も利用したスマートな作り方に変更する

  • スキンの格納方法の検証


    • 今回はサンプルなのでCharacter内部に情報をもたせているが、できれば外に出したい

    • MODやDLCも考慮した構造を考える必要がある



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


メモ


  • このサンプルプロジェクトを公開したかったが、軽い気持ちで使ったParagonアセットが超絶重いので今の所断念しています

  • Paragonアセットのおかげでパッケージビルドが一晩かかりました