最新のMirrorでは動かなくなっているようです。改めて調査し直して方法がまとまったら更新します。公式ドキュメントの場所も以下に変わっていました。
Mirrorは、UNetをベースに設計された高評価のMMOスケールにも対応できるというネットワークAPIライブラリです。
MirrorでデフォルトのNetworkManagerを使った場合、Player Prefab欄に設定されているプレハブをプレイヤー用のゲームオブジェクトとして自動的に生成します。NetworkManagerを継承すればプレイヤーごとに違うプレハブからプレイヤーを生成することができるという自分用のメモです。
目次
実行環境
- Unity2019.3.15f1
- Mirror16.1.1
ざっくり手順
プレイヤー用のプレハブを用意する
違いが分かるようにメッシュを変えたり動きを変えたプレイヤー用のプレハブをいくつか用意します。NetworkIdentity
をアタッチしてあれば一先ず動きます。
プレイヤー定義用のScriptableObjectを作成
クライアントからどのプレハブを使うかを送る手段が考えつかなかったので、ちょっと乱暴ですがScriptableObjectにプレイヤー用のプレハブを配列で持たせて、そのインデックスで生成するオブジェクトを指定するようにします(ResourcesやAssetBundleを使えば、プレハブ名などでいけると思います)。
PlayerPrefabList.cs
配列の参照用のenumと、GameObjectの配列を持つScriptableObjectを宣言しています。PlayerType
の内容は、用意するプレイヤー用プレハブに応じて書き換えてください。
using System.Collections; using System.Collections.Generic; using UnityEngine; public enum PlayerType { Green, Red } [CreateAssetMenu(menuName ="AM1Mirror/PlayerPrefabList")] public class PlayerPrefabList : ScriptableObject { public GameObject [] playerPrefs = null; }
これができたら、ProjectウィンドウのCreateから AM1Mirror > PlayerPrefabList を選んでスクリプタブルオブジェクトを作成します。作成したら、enumの定義順に応じてプレイヤー用のプレハブをInspectorウィンドウから設定しておきます。
NetworkManagerのサブクラスを作成する
公式ガイドの以下をもとに、今回の要であるカスタムのNetworkManagerを作ります。
https://mirror-networking.com/docs/Guides/GameObjects/SpawnPlayerCustom.htmlmirror-networking.com
using System.Collections; using System.Collections.Generic; using UnityEngine; using Mirror; public class NetworkManagerCP : NetworkManager { PlayerType spawnPlayerType; [Tooltip("プレイヤープレハブリスト"), SerializeField] PlayerPrefabList playerPrefabList = null; public void SetPlayerType(PlayerType pt) { spawnPlayerType = pt; } /// <summary> /// サーバー開始時、プレイヤーキャラクターのメッセージを登録 /// </summary> public override void OnStartServer() { base.OnStartServer(); NetworkServer.RegisterHandler<CreateCharacterMessage>(OnCreateCharacter); } /// <summary> /// クライアント側で接続した時に、選択してあるプレイヤーのプレハブをメッセージで送信 /// </summary> /// <param name="conn"></param> public override void OnClientConnect(NetworkConnection conn) { base.OnClientConnect(conn); CreateCharacterMessage ccm = new CreateCharacterMessage { playerType = spawnPlayerType }; conn.Send(ccm); } /// <summary> /// メッセージがクライアントからサーバーに到着したら、届いたプレハブでプレイヤー生成 /// </summary> /// <param name="conn"></param> /// <param name="messages"></param> void OnCreateCharacter(NetworkConnection conn, CreateCharacterMessage messages) { Transform tr = GetStartPosition(); GameObject go = Instantiate(playerPrefabList.playerPrefs[(int)messages.playerType], tr.position, tr.rotation); NetworkServer.AddPlayerForConnection(conn, go); } }
通信を開始する
通信を開始するためのクラスSimpleNetMan
を作成します。これはNetworkManagerCP
に統合できるのですが、設定がNetworkManager
の設定に埋もれるのが見辛いので分けました。
using System.Collections; using System.Collections.Generic; using UnityEngine; using Mirror; public class CreateCharacterMessage : MessageBase { public PlayerType playerType; } public class SimpleNetMan : MonoBehaviour { public enum ConnectType { Host, Client } [Tooltip("プレイヤーの種類"), SerializeField] PlayerType playerType = PlayerType.Green; [Tooltip("接続の種類"), SerializeField] ConnectType connectType = ConnectType.Host; [Tooltip("ホストのIPアドレス"), SerializeField] string ipAddress = "localhost"; NetworkManagerCP networkManager = null; private void Awake() { networkManager = GetComponent<NetworkManagerCP>(); networkManager.SetPlayerType(playerType); StartCoroutine(Online()); } IEnumerator Online() { yield return null; // 1フレーム待つ if (connectType == ConnectType.Host) { networkManager.StartHost(); } else { networkManager.networkAddress = ipAddress; networkManager.StartClient(); } } }
IEnumeratorでやってるのは、なんとなく1フレーム待った方が初期化が終ってそうでいいかな、という雰囲気でやったことです。Awake()内でやってもいいかも。
仕上げ
作成したSimpleNetMan
とNetworkManagerCP
をNetworkManager
のような名前のゲームオブジェクトに一緒にアタッチします。
SimpleNetMan
では、生成したいプレイヤータイプを設定して、ホストかクライアントか選択して、ホストのIPアドレスを設定します。これらのパラメーターを設定するメニューを用意すれば実行時に変更できます。
NetworkManagerCP
では、下の方にあるPlayer Prefab List
欄に、最初に作成したScriptableObjectをアタッチして、ScriptableObjectに設定したプレイヤー用のプレハブをRegistered Spawnable Prefabs欄に全て追加します。これをしないとプレイヤーオブジェクトがネットワーク上に生成できず、エラーになります。
まとめ
ざっくりですがこんな感じでできました。ScriptableObjectを利用するなど何らかの方法で、クライアントからホストへ生成したいプレイヤープレハブをNetworkMessageで送信して、それをもとにStartHost()やStartClient()が実行されたら、NetworkMessageを受け取って記録して、クライアントの生成の段階で指定されたプレハブからプレイヤーオブジェクトをInstantiateして、ネットワークに追加します。
この記事では、スクリプトが分かれていたり、Inspectorウィンドウでプレイヤーや接続の種類を設定する不自然な状態になっていますが、これは体験入学用に全員がUnityエディターで作業をすることを前提としているからです。通常の利用であれば、プレイヤーを選択するルームなどを作って、そこで選ばれたものをメッセージで送ってゲーム開始、という感じになると思います。
とりあえず、こんな流れで、こんな感じのコードで異なるプレハブからプレイヤーを生成できるという記事でした。
おまけ:Mirrorのイベントの発生順
重要なドキュメントですが、なんか下の方にあった...
https://mirror-networking.com/docs/Guides/Communications/NetworkManager.htmlmirror-networking.com
これを見ると、まずはStart()
が呼ばれて、OnStartServer()
はOnStartHost()
より後に呼ばれています。ということで、NetworkManagerのStartHost()やStartClient()で通信を開始します。