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スクリプトでは仮想メソッドが宣言されています。

それぞれのメソッドをoverrideで定義すれば特定のタイミングでそれらのメソッドが呼ばれるので、そこに自分で追加したい処理を追加する事が出来ます。
例えば新しいクライアントがサーバーに接続した時には
1 2 3 | public virtual void OnServerConnect(NetworkConnection conn); |
OnServerConnectメソッドが呼ばれます。
そこで自前のクラスで
1 2 3 4 | public override void OnServerConnect(NetworkConnection conn) { } |
と定義すればクライアントがサーバーに接続してきた時になんらかの処理を実行することが出来ます。
もっと細かく処理を行いたい場合は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にはインスペクタのAdd ComponentからUI→Toggle Groupを選択し取り付けます。
GrayEthan、RedEthanのGroupにはSelectCharacterPanelのToggle Groupをドラッグ&ドロップして設定し、この2つのToggleが1つしか選択出来ないようにします。
↑のような領域にSelectCharacterPanelを調整します。
ログイン画面LoginPanelの作成
NetworkUIの子要素にPanelを作成し、名前をLoginPanelとします。
LoginPanelは「ホストとして接続」「クライアントとして接続」「マッチングメーカーを起動」といった選択を行う領域として使用します。
LoginPanelの子要素にButtonを3つ作成し、名前をStartHostButton、StartClientButton、CreateMatchMakerButtonとします。
ここまでの階層は
↑のようになります。
実際のLoginPanelはSelectCharacterPanleの下側に配置します。
↑のように画面が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が表示された時はLoginPanelを非表示にしますが、↑では位置を確認する為、そのままLoginPanelも表示しています。
RoomListPanelのインスペクタでAdd ComponentからLayout→Vertical Layout Groupを選択し取り付けます。
↑のように設定します。
部屋情報プレハブの作成
RoomListPanelの子要素には部屋ごとの情報を表示します。
その為に部屋情報を持つゲームオブジェクトのプレハブを作成し、部屋数分だけインスタンス化し表示させます。
その為のプレハブを作成していきましょう。
RoomListPanelの子要素にPanelを作成し、名前をRoomDataとします。
RoomDataの子要素にTextを作成し、名前をRoomNameとします。
RoomDataの子要素にButtonを作成し、名前をEnterTheRoomButtonとします。
↑のような階層になります。
RoomDataは部屋ごとに作成されるのであまり大きくならないようにRoomDataの高さを調整します。
RoomDataの大きさは
↑のような感じにしました。
RoomDataはAssetsフォルダにドラッグ&ドロップしてプレハブにします。
ヒエラルキー上のRoomDataはいらないので削除します。
ゲームを終了するBackPanelの作成
NetworkUIの子要素にPanelを作成し名前をBackPanel、その子要素にButtonを作成し名前をBackButtonとします。
BackPanelはゲーム中にゲームを終了するボタンを表示する領域に使います。
BackPanelは
↑の右上の赤い四角の領域にサイズ調整します。
これでUIが出来上がりました。
ここまではネットワークと関係ない部分なのでパネル分けやボタン等の数が同じであればUIの大きさ等は適当に作っても問題ありません。
自前のネットワークマネージャーMyNetworkManagerスクリプトの作成
UIが出来たのでUIを操作した時に呼び出す処理をスクリプトに書いていきます。
NetworkUIゲームオブジェクトにMyNetworkManagerスクリプトを作成し取り付けます。
NetworkUIゲームオブジェクトにMyNetworkManagerスクリプトを取り付ける理由としてはMyNetworkManagerスクリプトはNetworkManagerスクリプトを継承して作成するので、DontDestroyOnLoad項目をチェックしておけば、ログインシーンからゲームシーンに移動した時、またはその逆での遷移で設定したゲームオブジェクトが削除されず引き継がれます。
MyNetworkManagerスクリプトを別の空のゲームオブジェクトに設定してもいいんですが、そうするとUIはログインシーンにしか存在せず一旦ゲームシーンに遷移してしまうとUIのボタンを押した時にMyNetworkManagerスクリプトのメソッドを呼び出している部分でMissingとなります。
ログインシーンが読み込まれるたびにスクリプトからイベントリスナーを登録する事も出来ますが面倒臭いことになるので、UIにMyNetworkManagerを取り付けてUI自身も引き継いだ方が簡単です。
というわけでNetworkUIにMyNetworkManagerスクリプトを取り付けています。
1 2 3 4 5 6 7 8 9 10 11 12 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking; using UnityEngine.Networking.Match; using UnityEngine.UI; using UnityEngine.Networking.NetworkSystem; public class MyNetworkManager : NetworkManager { } |
MyNetworkManagerはNetworkManagerクラスを拡張して作成します。
usingディレクティブでいっぱい記述されてますが、これらはこれから書いていくスクリプトで使用します。
フィールド宣言
まずはフィールド宣言部分を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // キャラクターセレクトパネル [SerializeField] private GameObject selectCharacterPanel; // ログインパネル [SerializeField] private GameObject loginPanel; // マッチメイカーパネル [SerializeField] private GameObject matchMakerPanel; // ルームリストパネル [SerializeField] private Transform roomListPanel; // ホストが繋ぐ前に戻るパネル [SerializeField] private GameObject backPanel; // 現在選択しているキャラクター [SerializeField] private int selectedCharaNum = 0; // ルームデータプレハブ [SerializeField] private GameObject roomDataPrefab; // ルームデータインスタンスを入れておく private List<GameObject> roomDataLists = new List<GameObject>(); // 情報表示用テキスト [SerializeField] private Text informationText; |
ほとんど先ほど作成したUIの領域を設定するフィールドです。
roomDataPrefabは個々の部屋の情報を持っているプレハブを設定します(先ほど作成したRoomData)。
roomDataListsは部屋情報を入れる為のリストです。
操作キャラクター切り替え処理
SelectCharacterPanel以下のGrayEthanとRedEtanのトグルを操作した時に呼び出す処理を記述します。
1 2 3 4 5 6 7 | // 操作キャラクター切り替えメソッド public void ChangeChara(int charaNum) { Debug.Log ("ChangeChara"); selectedCharaNum = charaNum; } |
GrayEthanのOnValueChangedは
↑のように0を渡すように設定します。
RedEthanは1を渡すようにします。
ホストとして起動の処理
次にStartHostButtonを押した時の処理を記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // ホストとして接続 public void CreateHost() { Debug.Log ("CreateHost"); bool noConnection = (client == null || client.connection == null || client.connection.connectionId == -1); if ((!IsClientConnected() && !NetworkServer.active && matchMaker == null) && noConnection) { selectCharacterPanel.SetActive (false); loginPanel.SetActive (false); StartHost (); backPanel.SetActive (true); } } |
よくわからない処理をしていますが・・・(^_^;)
元のNetworkManagerHUDで記述されていた処理を少し改造したものです。
条件が成立したら隠したいパネルを非表示にし、StartHostメソッドを呼び出します。
StartHostメソッドはNetworkManagerで定義されているのでそのまま呼び出すだけです。
その後backPanelを表示し、ゲームから離脱出来るようにします。
StartHostButtonのOnClickでCreateHostメソッドを呼び出すようにします。
クライアントとして起動の処理
StartClientButtonを押した時の処理を記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // クライアントとして接続 public void JoinGame() { Debug.Log ("JoinGame"); bool noConnection = (client == null || client.connection == null || client.connection.connectionId == -1); if ((!IsClientConnected() && !NetworkServer.active && matchMaker == null) && !noConnection) { backPanel.SetActive (false); StopClient (); selectCharacterPanel.SetActive (true); loginPanel.SetActive (true); } else { selectCharacterPanel.SetActive (false); loginPanel.SetActive (false); StartClient (); backPanel.SetActive (true); } } |
これまたNetworkManagerHUDの処理を改造したものです。
単に
1 2 3 4 5 6 7 8 | public void JoinGame() { selectCharacterPanel.SetActive (false); loginPanel.SetActive (false); StartClient (); backPanel.SetActive (true); } |
でも問題なさそう?
StartClientButtonのOnClickでJoinGameメソッドを呼び出すようにします。
CreateMatchMakerButtonを押した時の処理
CreateMatchMakerButtonを押した時の処理を記述します。
1 2 3 4 5 6 7 8 9 10 | // マッチングメーカーを使用 public void EnableMatchMaker() { Debug.Log ("EnableMatchMaker"); loginPanel.SetActive (false); matchMakerPanel.SetActive(true); StartMatchMaker (); FindTheRoom (); } |
UIの操作をした後NetworkManagerスクリプトのStartMatchMakerメソッドを呼び出しマッチングメーカーをスタートさせます。
FindTheGameメソッドは部屋を探す処理で後で作成します。
CreateMatchMakerButtonのOnClickでEnableMatchMakerメソッドを呼び出すようにします。
MatchMakerPanel以下の処理
この後はMatchMakerPanelの階層化のUIの操作で呼び出す処理を記述していきます。
部屋の名前を変更した時の処理
RoomNameのOnEndEdit(フィールドの編集を終えた時)で呼び出す処理です。
1 2 3 4 5 6 7 8 9 10 | // 部屋の名前を変更 public void ChangeRoomName(string roomName) { Debug.Log ("ChangeRoomName"); if (roomName == "") { roomName = "defaultRoom"; } matchName = roomName; } |
引数で受け取った名前をNetworkManagerのmatchNameに設定します。
RoomNameのOnEndEditでDynamic stringの方のChangeRoomNameを設定します。
Dynamic stringのChangeRoomNameを呼び出すとInputFieldで編集した文字列がそのままChangeRoomNameの引数に渡されます。
部屋の作成処理
部屋を作成する処理を記述していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 部屋の作成 public void CreateRoom() { Debug.Log ("CreateRoom"); // 部屋の名前、最大人数、ListMatchesで取得できるかどうか,パスワード、パブリックなクライアントアドレス、プライベートなクライアントアドレス、Eloスコア // リクエストドメイン、コールバック matchMaker.CreateMatch(matchName, matchSize, true, "", "", "", 0, 0, OnMatchCreate); } // 部屋が作成されると呼ばれる public override void OnMatchCreate(bool success, string extendedInfo, MatchInfo matchInfo) { Debug.Log ("OnMatchCreate"); selectCharacterPanel.SetActive (false); matchMakerPanel.SetActive (false); base.OnMatchCreate (success, extendedInfo, matchInfo); backPanel.SetActive (true); } |
CreateMatchButtonを押した時にCreateRoomメソッドを呼び出します。
NetworkManagerのmatchMakerのCreateMatchメソッドを呼び出すことで部屋を作成します。
matchNameやmatchSizeはNetworkManagerのインスペクタで設定している値です。
引数がいっぱいありますが・・・、詳しくはスクリプトリファレンスを参照してみてください。
CreateMatchの引数でOnMatchCreateを設定していますが、これはCreateMatchメソッドが実行された結果呼び出されるコールバックメソッドを指定しています。
自分で部屋を作成したらパネルの表示や非表示をし、すぐにゲームを開始します。
CreateMatchButtonのOnClickにCreateRoomメソッドを指定します。
存在する部屋を探す
既に存在する部屋を探す処理を記述していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | // 部屋を見つける public void FindTheRoom() { Debug.Log ("FindTheRoom"); // 初めのページ、マッチの数、マッチの名前のフィルター、プライベートなマッチを含むか、Eloスコア、リクエストドメイン、コールバック matchMaker.ListMatches(0, 10, "", true, 0, 0, OnMatchList); } // 部屋情報を取得すると呼ばれる public override void OnMatchList(bool success, string extendedInfo, List<MatchInfoSnapshot> matchList) { Debug.Log ("OnMatchList"); foreach (var item in roomDataLists) { Destroy (item); } roomDataLists.Clear (); foreach (var match in matchList) { var room = Instantiate (roomDataPrefab) as GameObject; room.transform.GetChild (0).GetComponent<Text> ().text = match.name; room.transform.GetChild (1).GetComponent<Button> ().onClick.AddListener (() => matchMaker.JoinMatch (match.networkId, "", "", "", 0, 0, OnMatchJoined)); room.transform.SetParent (roomListPanel); roomDataLists.Add (room); } base.OnMatchList(success, extendedInfo, matchList); } // 部屋に入室したら呼ばれる public override void OnMatchJoined(bool success, string extendedInfo, MatchInfo matchInfo) { Debug.Log ("OnMatchJoined"); if (success) { selectCharacterPanel.SetActive (false); loginPanel.SetActive (false); matchMakerPanel.SetActive (false); base.OnMatchJoined (success, extendedInfo, matchInfo); foreach (var item in roomDataLists) { Destroy (item); } roomDataLists.Clear (); backPanel.SetActive (true); } else { informationText.text = "既に部屋が閉じていたようです。再度部屋を検索します。"; FindTheRoom (); } } |
FindTheRoomButtonを押した時の処理がFindTheRoomメソッドです。
マッチメーカーのListMatchesメソッドで部屋を取得します。
そのコールバックメソッドがOnMatchListメソッドで、引数で受け取ったmatchListに個々の部屋の情報が入っています。
RoomDataプレハブをインスタンス化し、matchListから得た部屋の情報をRoomDataインスタンスのUIに設定しています。
インスタンス化したRoomDataの親はRoomListPanelに設定します。
RoomDataは部屋の数だけインスタンス化し、その場で子要素のEnterTheRoomButtonも配置されるためOnClickにあらかじめ呼び出す処理を設定する事が出来ません。
そこでインスタンス化した時に、
1 2 3 | room.transform.GetChild (1).GetComponent<Button> ().onClick.AddListener (() => matchMaker.JoinMatch (match.networkId, "", "", "", 0, 0, OnMatchJoined)); |
とAddListenerでイベントリスナーを登録しています。
イベントリスナーについては

を参照してください。
↑の記事ではやっていませんが、ラムダ式で呼び出すメソッドに引数を渡すには先ほどのスクリプトのようにします。
登録したイベントリスナーのボタンが押されるとその部屋に入室しますが、その時にコールバックで指定したOnMatchJoinedが呼ばれます。
部屋に入室出来た時はパネルの表示・非表示をしますが、失敗した時はメッセージを表示し、FindTheRoomメソッドを呼び出して再度部屋情報を検索します。
FindTheMatchButtonのOnClickでFindTheRoomメソッドを指定します。
マッチング画面から抜ける処理
マッチングメーカーを使った画面から最初の画面に戻る処理を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // マッチメーカーを終了 public void DisableMatchMaker() { Debug.Log ("DisableMatchMaker"); matchMakerPanel.SetActive (false); StopMatchMaker (); foreach (var item in roomDataLists) { Destroy (item); } roomDataLists.Clear (); loginPanel.SetActive (true); } |
NetworkManagerスクリプトのStopMatchMakerメソッドを呼び出しマッチングメーカーを終了します。
BackToLoginButtonのOnClickでDisableMatchMakerメソッドを呼び出すようにします。
ゲームからログアウトする処理
ゲーム中にゲームから離脱する処理を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | // ゲームを終了する public void EndGame() { Debug.Log ("EndGame"); backPanel.SetActive (false); StopHost (); selectCharacterPanel.SetActive (true); loginPanel.SetActive (true); } |
NetworkManagerのStopHostメソッドを呼び出して終了します。
StopHostはホストとクライアント両方を停止しますが、ホストとして起動していたユーザーが離脱すると他のクライアントも離脱することになります。
BackButtonのOnClickでEndGameメソッドを呼び出すようにします。
操作キャラクター変更処理
次に操作キャラクターを変更する処理を記述していきます。
SelectCharacterPanelの子要素のトグルで操作した場合、操作キャラクターの番号を切り替えていますが、ネットワーク上にインスタンス化する操作キャラクターは変更していません。
そこで操作キャラクターを変更する処理を書いていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | // サーバーにクライアントのプレイヤーが追加された public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader reader) { Debug.Log ("OnServerAddPlayer"); // NetworkReaderからプレイヤー番号を取得 IntegerMessage msg = reader.ReadMessage<IntegerMessage> (); var playerSpawnPos = Vector3.zero; var player = GameObject.Instantiate(spawnPrefabs[msg.value], playerSpawnPos, Quaternion.identity) as GameObject; NetworkServer.AddPlayerForConnection(conn, player, playerControllerId); } // クライアントのシーンが変更された public override void OnClientSceneChanged(NetworkConnection conn) { Debug.Log ("OnClientSceneChanged"); // 現在選択中のプレイヤー番号をIntegerMessageとして作成 IntegerMessage msg = new IntegerMessage(selectedCharaNum); // always become ready. if (!ClientScene.ready) { ClientScene.Ready (conn); } if (!autoCreatePlayer) { return; } bool addPlayer = (ClientScene.localPlayers.Count == 0); bool foundPlayer = false; for (int i = 0; i < ClientScene.localPlayers.Count; i++) { if (ClientScene.localPlayers[i].gameObject != null) { foundPlayer = true; break; } } if (!foundPlayer) { // there are players, but their game objects have all been deleted addPlayer = true; } if (addPlayer) { // メッセージ付きのAddPlayerを呼び出す ClientScene.AddPlayer(conn, 0, msg); } } // サーバーに接続した時にクライアントで呼び出される public override void OnClientConnect(NetworkConnection conn) { Debug.Log ("OnClientConnect"); // 現在選択中のプレイヤー番号をIntegerMessageとして作成 IntegerMessage msg = new IntegerMessage(selectedCharaNum); if (!clientLoadedScene) { if (!ClientScene.ready) { ClientScene.Ready (conn); } if (NetworkManager.singleton.autoCreatePlayer) { // プレイヤー番号を送れるように複数の引数を取るAddPlayerを使用 ClientScene.AddPlayer(conn, 0, msg); } } } |
クライアントの操作キャラクターをサーバーに追加する時は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はネットワークでインスタンス化するプレハブを設定する項目で、アイテムや敵、銃の弾等を設定したりします。
↑のように操作キャラクターを設定します。
Player1はGrayEthanで、Player2がRedEthanです。
キャラクターをインスタンス化するのはOnClientSceneChangedメソッドとOnClientConnectメソッド内なので、そこでAddPlayerメソッドの第3引数にキャラクター番号を指定し呼び出しています。
1 2 3 | ClientScene.Ready(conn) |
で呼び出すとたまにすでにクライアントの準備は出来ています的なエラーが出たので
1 2 3 4 5 | if (!ClientScene.ready) { ClientScene.Ready (conn); } |
↑のように準備が出来ていないときだけ呼び出すようにしました。
OnServerAddPlayer、OnClientSceneChanged、OnClientConnectはNetworkManagerのメソッドを上書きしています。
操作キャラクターを切り替える機能は
こちらを参考にさせて頂きました。
動画が削除されている・・・・(^_^;)
コネクションが切断された時の処理
最後にサーバーが切断した時とクライアントがサーバーから切断した時の処理を書いていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // サーバーが切断した時にクライアントで呼び出される public override void OnClientDisconnect(NetworkConnection conn) { Debug.Log ("OnClientDisConnect"); backPanel.SetActive (false); StopClient (); // base.OnClientDisconnect(conn); selectCharacterPanel.SetActive (true); loginPanel.SetActive (true); } // クライアントがサーバーから切断された時に呼び出される public override void OnServerDisconnect(NetworkConnection conn) { Debug.Log ("OnServerDisconnect"); NetworkServer.DestroyPlayersForConnection(conn); } |
OnClientDisconnectメソッドはサーバーが切断した時にクライアントで呼ばれます。
NetworkManagerのOnClientDisconnectメソッドだとTimeoutエラーが出るのでbase.OnClientDisconnect(conn)は呼び出さず単純に
1 2 3 | StopClient(); |
メソッドだけを呼び出してクライアントの終了だけ行うようにしました。
OnServerDisconnectはクライアントがサーバーから切断された時に呼ばれるメソッドですがこちらでも同じようにTimeoutエラーが出たので、親のメソッドは呼ばずに
1 2 3 | NetworkServer.DestroyPlayersForConnection(conn); |
だけ呼び出しました。
これで全ての機能が完成しました!
NetworkUIに設定したMyNetworkManagerのインスペクタは
のようにします。
実行すると記事の最初の示した動画のようになります。
終わりに
NetworkManagerをカスタマイズして自作のUIでオンラインの機能を試しましたが、細かい処理をさせようと思うとさらに内部的な構造を理解しなくてはいけなくなるため大変ですね・・・・(^_^;)
でも最低限の機能は出来たかなぁとは思います・・・・(-_-)
細かいUIの初期化等はしてないところもあったり、オンラインに接続した時のキャラクターの制御は行っていません。