UnityNetworkでオンライン対応10-NetworkManagerを拡張して自前のUIから操作-

UNetでオンライン対応のゲームを作成する時にNetworkManagerやNetworkLobbyManagerを使って開発していく事が多いと思いますが、

NetworkLobbyアセットをアセットストアからインポートした場合はヒエラルキー上のUIのゲームオブジェクトを取り換えるだけで、ある程度自前のUIに切り替えられます。

しかしNetworkManagerでオンライン対応をしていてNetworkManagerHUDでUIを表示している場合はスクリプトからUIを出力している為、そこから自前のUIに切り替えるのが大変です。

NetworkManagerHUDがどのようにUIを出力し、どのようにNetworkManagerの処理を呼び出しているのかがわからないからです。((+_+))

わたくしもどうやったらいいのかわからなくなった一人です。(._.)

そんなわけで、今回は自前のUIを使ってNetworkManagerHUDに近い機能を作成していきたいと思います。

NetworkLobbyManagerはNetworkManagerを拡張したものなので処理が複雑です。

またNetworkLobbyManagerはゲームがスタートした後の部屋に新しいメンバーが入室出来ません(改造すれば出来そうな気もしますが)。

そこで機能がシンプルなNetworkManagerクラスを拡張して自前のネットワークマネージャークラスを作成し、その処理を自前のUIから呼び出すようにしていきます。

スポンサーリンク

NetworkManagerやNetworkManagerHUDのスクリプト

自前のUIを使ったオンライン対応をする時に難しく感じるのは内部の処理が見えないからです。

NetworkManagerやNetworkLobbyManager、NetworkManagerHUDはスクリプトを表示出来ないので、結局最初から自分でスクリプトを組んでいかなくてはいけなくなり難易度が高くなりますね・・・(^_^;)

と思ってたんですが、Unityさんの方でスクリプトをダウンロード出来るようです。

これでNetworkManager、NetworkLobbyManager、NetworkManagerHUDの中身が解析出来ますね。

解析も大変そうですが・・・。

NetworkManagerHUDの中身を見ることが出来るので、これを元に自前のUIのボタンを押した時に同じような処理を作成し呼び出せば対応出来そうです。

拡張したクラスでする事

NetworkManagerスクリプトでネットワーク対応が出来ている(一部バグがあるみたいですが・・・)のでNetworkManagerを拡張して作成する自前のクラスでは親のNetworkManagerクラスのメソッドを呼び出し、それに自分で追加したい処理を加えるのが良さそうです。

NetworkManagerスクリプトでは仮想メソッドが宣言されています。

Network Manager は、マルチプレイヤーゲームのネットワーク状態を管理するためのコンポーネントです。完全に高レベル API (HLAPI) によって実装されているため、NetworkManager によって行われるすべての作業はスクリプトを通して行うことが可能です。ただし NetworkManager コン...

それぞれのメソッドをoverrideで定義すれば特定のタイミングでそれらのメソッドが呼ばれるので、そこに自分で追加したい処理を追加する事が出来ます。

例えば新しいクライアントがサーバーに接続した時には

OnServerConnectメソッドが呼ばれます。

そこで自前のクラスで

と定義すればクライアントがサーバーに接続してきた時になんらかの処理を実行することが出来ます。

もっと細かく処理を行いたい場合はNetworkManagerに頼らず自分で内部を実装していく必要がありそうですね・・・・((+_+))

今回作成する機能について

今回作成するのはNetworkManagerHUDで表示される機能の「ホストとして起動」「クライアントとして接続」「マッチングメーカーを使ったインターネットマッチ」の部分を作成していきます。

ホストで起動は特にいらないかなと思いまして、ボタンは作成しません・・・・。

基本的にはNetworkManagerスクリプトの機能で対応しますが、一部バグなのかわかりませんが不具合が出るので適当に処理を変えました・・・・(この場合の適当は雑にということで・・・)。

ローカル上でホストを2個起動するとポートが被ってエラーになりますが、これは元々そうなっているのでそのままにしておきます。

↑のように自前のUIでオンライン接続処理を行えるようになりました。

自前のネットワーク機能を作成

それでは自前のネットワーク機能を作成していきましょう。

ネットワーク用のUIを作成

まずはネットワーク処理用のUIを作成していきます。

今まではNetworkManagerHUDスクリプトで生成されていたUIを自分で作っていきます。

UIを操作した時に呼び出すスクリプトは後で作成しますので、スクリプトが出来上がったらそれぞれ対応する処理を呼び出すことにします。

ヒエラルキー上にCanvasを作成し名前をNetworkUIとします。

同時にEventSystemが作成されると思いますが、NetworkUIの子要素に移動させておきます(シーン移動で消えないように)。

キャラクター選択領域SelectCharacterPanelの作成

NetworkUIの子要素にPanelを作成し、名前をSelectCharacterPanelとします。

SelectCharacterPanelはキャラクターを選択する領域として使います。

その子要素にToggleを2つ作成し、名前をGrayEthanとRedEthanとします。

それぞれの子要素のTextにキャラクターの名前を入れておきます。

SelectCharacterPanelの階層

↑のような階層になります。

SelectCharacterPanelにはインスペクタのAdd ComponentからUI→Toggle Groupを選択し取り付けます。

GrayEthan、RedEthanのGroupにはSelectCharacterPanelのToggle Groupをドラッグ&ドロップして設定し、この2つのToggleが1つしか選択出来ないようにします。

SelectCharacterPanelの実際の画面

↑のような領域にSelectCharacterPanelを調整します。

ログイン画面LoginPanelの作成

NetworkUIの子要素にPanelを作成し、名前をLoginPanelとします。

LoginPanelは「ホストとして接続」「クライアントとして接続」「マッチングメーカーを起動」といった選択を行う領域として使用します。

LoginPanelの子要素にButtonを3つ作成し、名前をStartHostButton、StartClientButton、CreateMatchMakerButtonとします。

ここまでの階層は

LoginPanelの階層

↑のようになります。

実際のLoginPanelはSelectCharacterPanleの下側に配置します。

LoginPanelの実際の画面

↑のように画面がSelectCharacterPanelとLoginPanelで埋め尽くされるようにします。

マッチングメーカー画面のMatchMakerPanelの作成

MatchMakerPanelはLoginPanelのCreateMatchMakerButtonを押した時に開く画面で、部屋を作成したり既に存在する部屋を表示したりとマッチングに関する画面の処理をする領域です。

NetworkUIの子要素にPanelを作成し名前をMatchMakerPanelとします。

その子要素にPanelを作成し、名前をMenuPanelとします。

MenuPanelの子要素にInputFieldを作成し、名前をRoomNameとします。

RoomNameはその名の通り部屋を作成する時の部屋の名前を入力するフィールドです。

MenuPanelの子要素にButtonを3つ作成し、名前をCreateMatchButton、FindTheMatchButton、BackToLoginButton、とします。

3つのボタンは部屋の作成、部屋を探す、最初の画面に戻る処理を呼び出すボタンです。

MenuPanelの子要素にPanelを作成し、名前をRoomListPanelとします。

RoomListPanelの子要素に部屋情報を表示するようにします。

MenuPanelの子要素にPanelを作成し、名前をInformationPanelとしその子要素にTextを作成します。

Textには部屋に入室した時に既に部屋がなかった場合の情報を表示するようにします。

これでMatchMakerPanelが出来ました。

MatchMakerPanelの階層

実際に出来た画面は

MatchMakerPanelの実際の画面

↑のようになります。

MatchMakerPanelが表示された時はLoginPanelを非表示にしますが、↑では位置を確認する為、そのままLoginPanelも表示しています。

RoomListPanelのインスペクタでAdd ComponentからLayout→Vertical Layout Groupを選択し取り付けます。

VerticalLayoutGroupの設定

↑のように設定します。

部屋情報プレハブの作成

RoomListPanelの子要素には部屋ごとの情報を表示します。

その為に部屋情報を持つゲームオブジェクトのプレハブを作成し、部屋数分だけインスタンス化し表示させます。

その為のプレハブを作成していきましょう。

RoomListPanelの子要素にPanelを作成し、名前をRoomDataとします。

RoomDataの子要素にTextを作成し、名前をRoomNameとします。

RoomDataの子要素にButtonを作成し、名前をEnterTheRoomButtonとします。

RoomDataの階層

↑のような階層になります。

RoomDataは部屋ごとに作成されるのであまり大きくならないようにRoomDataの高さを調整します。

RoomDataの高さ

RoomDataの大きさは

RoomDataの実際の画面

↑のような感じにしました。

RoomDataはAssetsフォルダにドラッグ&ドロップしてプレハブにします。

ヒエラルキー上のRoomDataはいらないので削除します。

ゲームを終了するBackPanelの作成

NetworkUIの子要素にPanelを作成し名前をBackPanel、その子要素にButtonを作成し名前をBackButtonとします。

BackPanelはゲーム中にゲームを終了するボタンを表示する領域に使います。

BackPanelの階層

BackPanelは

BackPanelの実際の画面

↑の右上の赤い四角の領域にサイズ調整します。

これでUIが出来上がりました。

ここまではネットワークと関係ない部分なのでパネル分けやボタン等の数が同じであればUIの大きさ等は適当に作っても問題ありません。

自前のネットワークマネージャーMyNetworkManagerスクリプトの作成

UIが出来たのでUIを操作した時に呼び出す処理をスクリプトに書いていきます。

NetworkUIゲームオブジェクトにMyNetworkManagerスクリプトを作成し取り付けます。

NetworkUIゲームオブジェクトにMyNetworkManagerスクリプトを取り付ける理由としてはMyNetworkManagerスクリプトはNetworkManagerスクリプトを継承して作成するので、DontDestroyOnLoad項目をチェックしておけば、ログインシーンからゲームシーンに移動した時、またはその逆での遷移で設定したゲームオブジェクトが削除されず引き継がれます。

MyNetworkManagerスクリプトを別の空のゲームオブジェクトに設定してもいいんですが、そうするとUIはログインシーンにしか存在せず一旦ゲームシーンに遷移してしまうとUIのボタンを押した時にMyNetworkManagerスクリプトのメソッドを呼び出している部分でMissingとなります。

ログインシーンが読み込まれるたびにスクリプトからイベントリスナーを登録する事も出来ますが面倒臭いことになるので、UIにMyNetworkManagerを取り付けてUI自身も引き継いだ方が簡単です。

というわけでNetworkUIにMyNetworkManagerスクリプトを取り付けています。

MyNetworkManagerはNetworkManagerクラスを拡張して作成します。

usingディレクティブでいっぱい記述されてますが、これらはこれから書いていくスクリプトで使用します。

フィールド宣言

まずはフィールド宣言部分を作成します。

ほとんど先ほど作成したUIの領域を設定するフィールドです。

roomDataPrefabは個々の部屋の情報を持っているプレハブを設定します(先ほど作成したRoomData)。

roomDataListsは部屋情報を入れる為のリストです。

操作キャラクター切り替え処理

SelectCharacterPanel以下のGrayEthanとRedEtanのトグルを操作した時に呼び出す処理を記述します。

GrayEthanのOnValueChangedは

GrayEthanのOnValueChangedの設定

↑のように0を渡すように設定します。

RedEthanは1を渡すようにします。

ホストとして起動の処理

次にStartHostButtonを押した時の処理を記述します。

よくわからない処理をしていますが・・・(^_^;)

元のNetworkManagerHUDで記述されていた処理を少し改造したものです。

条件が成立したら隠したいパネルを非表示にし、StartHostメソッドを呼び出します。

StartHostメソッドはNetworkManagerで定義されているのでそのまま呼び出すだけです。

その後backPanelを表示し、ゲームから離脱出来るようにします。

StartHostButtonのOnClickでCreateHostメソッドを呼び出すようにします。

StartHostButtonのOnClickの設定

クライアントとして起動の処理

StartClientButtonを押した時の処理を記述します。

これまたNetworkManagerHUDの処理を改造したものです。

単に

でも問題なさそう?

StartClientButtonのOnClickでJoinGameメソッドを呼び出すようにします。

StartClientButtonのOnClickの設定

CreateMatchMakerButtonを押した時の処理

CreateMatchMakerButtonを押した時の処理を記述します。

UIの操作をした後NetworkManagerスクリプトのStartMatchMakerメソッドを呼び出しマッチングメーカーをスタートさせます。

FindTheGameメソッドは部屋を探す処理で後で作成します。

CreateMatchMakerButtonのOnClickでEnableMatchMakerメソッドを呼び出すようにします。

CreateMatchMakerButtonのOnClickの設定

MatchMakerPanel以下の処理

この後はMatchMakerPanelの階層化のUIの操作で呼び出す処理を記述していきます。

部屋の名前を変更した時の処理

RoomNameのOnEndEdit(フィールドの編集を終えた時)で呼び出す処理です。

引数で受け取った名前をNetworkManagerのmatchNameに設定します。

RoomNameのOnEndEditでDynamic stringの方のChangeRoomNameを設定します。

RoomNameのOnEndEditの設定

Dynamic stringのChangeRoomNameを呼び出すとInputFieldで編集した文字列がそのままChangeRoomNameの引数に渡されます。

部屋の作成処理

部屋を作成する処理を記述していきます。

CreateMatchButtonを押した時にCreateRoomメソッドを呼び出します。

NetworkManagerのmatchMakerのCreateMatchメソッドを呼び出すことで部屋を作成します。

matchNameやmatchSizeはNetworkManagerのインスペクタで設定している値です。

引数がいっぱいありますが・・・、詳しくはスクリプトリファレンスを参照してみてください。

この関数は新しいマッチの作成に使用します。この関数を呼び出すクライアントがマッチのホストになります。

CreateMatchの引数でOnMatchCreateを設定していますが、これはCreateMatchメソッドが実行された結果呼び出されるコールバックメソッドを指定しています。

自分で部屋を作成したらパネルの表示や非表示をし、すぐにゲームを開始します。

CreateMatchButtonのOnClickにCreateRoomメソッドを指定します。

CreateMatchButtonのOnClickの設定

存在する部屋を探す

既に存在する部屋を探す処理を記述していきます。

FindTheRoomButtonを押した時の処理がFindTheRoomメソッドです。

マッチメーカーのListMatchesメソッドで部屋を取得します。

そのコールバックメソッドがOnMatchListメソッドで、引数で受け取ったmatchListに個々の部屋の情報が入っています。

RoomDataプレハブをインスタンス化し、matchListから得た部屋の情報をRoomDataインスタンスのUIに設定しています。

インスタンス化したRoomDataの親はRoomListPanelに設定します。

RoomDataは部屋の数だけインスタンス化し、その場で子要素のEnterTheRoomButtonも配置されるためOnClickにあらかじめ呼び出す処理を設定する事が出来ません。

そこでインスタンス化した時に、

とAddListenerでイベントリスナーを登録しています。

イベントリスナーについては

Unityでスクリプトからボタン操作のイベントリスナーを取り付けるようにしてみます。

を参照してください。

↑の記事ではやっていませんが、ラムダ式で呼び出すメソッドに引数を渡すには先ほどのスクリプトのようにします。

登録したイベントリスナーのボタンが押されるとその部屋に入室しますが、その時にコールバックで指定したOnMatchJoinedが呼ばれます。

部屋に入室出来た時はパネルの表示・非表示をしますが、失敗した時はメッセージを表示し、FindTheRoomメソッドを呼び出して再度部屋情報を検索します。

FindTheMatchButtonのOnClickでFindTheRoomメソッドを指定します。

FindTheMatchButtonのOnClickの設定

マッチング画面から抜ける処理

マッチングメーカーを使った画面から最初の画面に戻る処理を作成します。

NetworkManagerスクリプトのStopMatchMakerメソッドを呼び出しマッチングメーカーを終了します。

BackToLoginButtonのOnClickでDisableMatchMakerメソッドを呼び出すようにします。

BackToLoginButtonのOnClickの設定

ゲームからログアウトする処理

ゲーム中にゲームから離脱する処理を作成します。

NetworkManagerのStopHostメソッドを呼び出して終了します。

StopHostはホストとクライアント両方を停止しますが、ホストとして起動していたユーザーが離脱すると他のクライアントも離脱することになります。

BackButtonのOnClickでEndGameメソッドを呼び出すようにします。

BackButtonのOnClickの設定

操作キャラクター変更処理

次に操作キャラクターを変更する処理を記述していきます。

SelectCharacterPanelの子要素のトグルで操作した場合、操作キャラクターの番号を切り替えていますが、ネットワーク上にインスタンス化する操作キャラクターは変更していません。

そこで操作キャラクターを変更する処理を書いていきましょう。

クライアントの操作キャラクターをサーバーに追加する時はOnServerAddPlayerメソッドが呼ばれます。

OnServerAddPlayerメソッドは第3引数でNetworkReader型の引数を受け取ることが出来ますので、キャラクターを追加する処理であるClientSceneのAddPlayerメソッドでキャラクター番号を渡すことにします。

その為、IntegerMessage型にキャラクター番号を変更し、AddPlayerメソッドの第3引数に渡します。

OnServerAddPlayerメソッド内で受け取ったNetworkReader型の引数をIntegerMessage型に変換し、その番号に応じたspawnPrefabsに設定したキャラクターをインスタンス化します。

spawnPrefabsはMyNetworkManagerのインスペクタで設定出来るので、操作キャラクターのGrayEthanプレハブを0番目、RedEthanプレハブを1番目に設定します。

Registerd Spawn PrefabsがspawnPrefabsになります。

Registerd Spawn Prefabsはネットワークでインスタンス化するプレハブを設定する項目で、アイテムや敵、銃の弾等を設定したりします。

RegisterdSpawnPrefabsに操作キャラクターを設定

↑のように操作キャラクターを設定します。

Player1はGrayEthanで、Player2がRedEthanです。

キャラクターをインスタンス化するのはOnClientSceneChangedメソッドとOnClientConnectメソッド内なので、そこでAddPlayerメソッドの第3引数にキャラクター番号を指定し呼び出しています。

で呼び出すとたまにすでにクライアントの準備は出来ています的なエラーが出たので

↑のように準備が出来ていないときだけ呼び出すようにしました。

OnServerAddPlayer、OnClientSceneChanged、OnClientConnectはNetworkManagerのメソッドを上書きしています。

操作キャラクターを切り替える機能は

こちらを参考にさせて頂きました。

動画が削除されている・・・・(^_^;)

コネクションが切断された時の処理

最後にサーバーが切断した時とクライアントがサーバーから切断した時の処理を書いていきます。

OnClientDisconnectメソッドはサーバーが切断した時にクライアントで呼ばれます。

NetworkManagerのOnClientDisconnectメソッドだとTimeoutエラーが出るのでbase.OnClientDisconnect(conn)は呼び出さず単純に

メソッドだけを呼び出してクライアントの終了だけ行うようにしました。

OnServerDisconnectはクライアントがサーバーから切断された時に呼ばれるメソッドですがこちらでも同じようにTimeoutエラーが出たので、親のメソッドは呼ばずに

だけ呼び出しました。

これで全ての機能が完成しました!

NetworkUIに設定したMyNetworkManagerのインスペクタは

MyNetworkManagerの設定

のようにします。

実行すると記事の最初の示した動画のようになります。

終わりに

NetworkManagerをカスタマイズして自作のUIでオンラインの機能を試しましたが、細かい処理をさせようと思うとさらに内部的な構造を理解しなくてはいけなくなるため大変ですね・・・・(^_^;)

でも最低限の機能は出来たかなぁとは思います・・・・(-_-)

細かいUIの初期化等はしてないところもあったり、オンラインに接続した時のキャラクターの制御は行っていません。

スポンサーリンク

記事をシェアして頂ける方はこちら

フォローして頂くとやる気が出ます