NetworkManager を使用する
カスタムの Spawn 関数

オブジェクトの Spawn(生成)

Unity では、Instantiate() を使用して新しいゲームオブジェクトを作成することを “spawning” と呼ぶことがあります。ネットワーク “HLAPI” 上では、“spawn” という言葉は、特有の限定された意味を持ちます。ネットワーク HLAPI のコンテンツ(権限)サーバーモデルでは、サーバー上にオブジェクトを “spawn(生成)” するということは、そのオブジェクトがサーバーに接続されたクライアント上に作成され、Spawning System(オブジェクト生成システム)によって管理されることを意味します。Spawning System に取り込まれると、オブジェクトがサーバー上で変化するたびにクライアントに状態のアップデートが送られ、オブジェクトがサーバー上で破壊されるとクライアント上でも破壊されます。また、Spawn されたオブジェクトは、サーバーが管理しているネットワーク オブジェクトのセットにも加えられるので、後で新しいクライアントがゲームに参加した場合には、そのクライアント上にもオブジェクトが Spawn されます。これらのオブジェクトは “netId” と呼ばれる固有のネットワーク インスタンス ID を持っています。これはオブジェクトごとに、サーバー上でもクライアント上でも同じ ID になります。これは、オブジェクトにメッセージを送ったり、オブジェクトを識別するために使用されます。

NetworkIdentity オブジェクトがクライアント上に Spawn(生成)されるときサーバー上におけるそのオブジェクトの現在の状態と同じ状態で生成されます。これにはオブジェクトの変形状態、動きの状態と同期された変数も含まれます。つまり、クライアントオブジェクトは作成されるときは常に最新の状態です。このおかげで、オブジェクトが間違って開始位置に生成されてしまい更新パケットが到着してから正しい位置に現れる、といった問題が回避されます。

これは非常に役立ちますが、それではオブジェクトはどのようにクライアント上に作成されるのでしょうか。そして、もしオブジェクトが生成されてから新しいクライアントが接続するまでの間にオブジェクトが変化した場合はどうなるのでしょうか。新しいクライアント上には、どちらのバージョンのオブジェクトが生成されるのでしょうか。

オブジェクトがクライアント上で Spawn するとき、サーバー上で NetworkServer.Spawn にパスされたオブジェクトのプレハブからクライアント オブジェクトがインスタンス化されます。NetworkIdentity インスペクターのプレビューパネルには NetworkIdentity のアセット ID が表示されますが、この値によってプレハブが識別されることで、クライアントはオブジェクトを作成することができます。このシステムが効率よく機能するために、クライアントによって実行されなければならない登録ステップがあります。クライアントは ClientScene.RegisterPrefab を呼び出し、クライアントオブジェクトが作成される元になるアセットの情報をシステムに伝える必要があります。

Spawn プレハブの登録には、エディターの NetworkManager を使用するのが最も便利です。NetworkManager の “Spawn Info” セクションで、一切コードを書かずにプレハブを登録できます。これは NetworkClient の作成時にコードによって行うこともできます。コードで行う方法は以下のとおりです。

using UnityEngine;
using UnityEngine.Networking;

public class MyNetworkManager : MonoBehaviour 
{
    public GameObject alienPrefab;
    
    NetworkClient myClient;

    // Create a client and connect to the server port
    public void SetupClient()
    {
        ClientScene.RegisterPrefab(alienPrefab);

        myClient = new NetworkClient();

        myClient.RegisterHandler(MsgType.Connect, OnConnected);
        myClient.Connect("127.0.0.1", 4444);
    }
}

この参考例では、ユーザーはプレハブアセットを MyNetworkManager スクリプトの alienPrefab スロットにドラッグすることになります。これは、サーバーにエイリアン オブジェクトが Spawn(生成)されたときに各クライアントにも同様のオブジェクトが作成されるようにするためです。このようにアセットを登録することで確実にアセットがシーンと一緒にロードされるようになり、アセットが作成されたときにそのロードに時間が掛からなくなります。オブジェクトのプールや、アセットが動的に作成された場合などの高度な使用方法としては、ClientScene.RegisterSpawnHandler があります。これは、クライアント側での Spawn(生成)のためにコールバック関数を登録できるようにするものです。

以下の簡単な例では、ランダムな数の葉をつくる1本の木を Spawn(生成)させています。

class Tree : NetworkBehaviour
{
    [SyncVar]
    public int numLeaves;
}

class MySpawner : NetworkBehaviour
{
    public GameObject treePrefab;

    public void Spawn()
    {
        GameObject tree = (GameObject)Instantiate(treePrefab, transform.position, transform.rotation);
        tree.GetComponent<Tree>().numLeaves = Random.Range(10,200);
        NetworkServer.Spawn(tree);
    }
}

この参考例を完成させるには、プロジェクトに、Tree スクリプトと NetworkIdentity コンポーネントを持った木のプレハブアセットを持たせます。そしてシーン内の MySpawner インスタンスで treePrefab スロットに木のプレハブアセットを追加します。また、木のプレハブは Spawn 可能なオブジェクトとして登録される必要があります。これは NetworkManager UI か ClientScene.RegisterPrefab() をコードで使用して行います。

このコードが実行されると、クライアントに作成された木のオブジェクトはサーバーから送られた正しい numLeaves の値を持つようになります。

制約

  • NetworkIdentity は Spawn 可能なプレハブのルートであるゲームオブジェクトにアタッチする必要があります。
  • NetworkBehaviour スクリプトは子ゲームオブジェクトではなく NetworkIdentity と同じゲームオブジェクトにアタッチする必要があります。
  • プレハブは、そのルートオブジェクトに NetworkIdentity がある場合以外は、NetworkManager とともに登録することはできません。

オブジェクト作成の流れ

Spawning(生成)の処理手順は以下のとおりです。

  • NetworkIdentity コンポーネントのプレハブが Spawn 可能なオブジェクトとして登録される
  • GameObject がサーバー上のプレハブからインスタンス化される
  • ゲームコードがインスタンスに初期値を設定する(注: ここで 3D の物理演算によって加えられる力は即時には効力を発しません)
  • NetworkServer.Spawn() がインスタンスとともに呼び出される
  • サーバー上のインスタンスの SyncVar の状態が NetworkBehaviour コンポーネントの OnSerialize() の呼び出しにより受け取られる
  • ネットワークメッセージである MsgType.ObjectSpawn が、SyncVar データを含む接続されたクライアントに送信される
  • OnStartServer() がサーバー上のインスタンスで呼び出され、isServer が True に設定される
  • 各クライアントが ObjectSpawn メッセージを受け取り、登録されたプレハブから新しいインスタンスを 1 つずつ作成する
  • NetworkBehaviour コンポーネント の OnDeserialize() を呼び出すことによって SyncVar データがクライアント上の新しいインスタンスに適用される
  • OnStartClient() が各クライアントのインスタンスで呼び出され、isClient が True に設定される
  • ゲームプレイの進行にしたがって、SyncVar の値の変更が自動的にクライアントに同期される。これがゲーム終了まで継続される。
  • NetworkServer.Destroy() がサーバー上のインスタンスで呼び出される
  • ネットワークメッセージである MsgType.ObjectDestroy が、各クライアントに送信される
  • OnNetworkDestroy() がクライアント上のインスタンスに呼び出され、インスタンスが破壊される

プレイヤー オブジェクト

ネットワーク HLAPI のプレイヤー オブジェクトは特殊な面を持っています。NetworkManager によるプレイヤーオブジェクトの Spawning (生成)の流れは以下のとおりです。

  • NetworkIdentity のあるプレハブが PlayerPrefab として登録される
  • クライアントがサーバーに接続される
  • クライアントが AddPlayer() を呼び出し、ネットワークメッセージである MsgType.AddPlayer が、サーバーに送られる
  • サーバーがメッセージを受信し NetworkManager.OnServerAddPlayer() を呼び出す
  • GameObject がサーバーの PlayerPrefab からインスタンス化される
  • NetworkManager.AddPlayerForConnection() がサーバー上の新しいプレイヤーインスタンスとともに呼び出される
  • プレイヤーインスタンスが Spawn (生成)される(プレイヤーインスタンス用に NetworkServer.Spawn() を呼び出す必要はありません。)
  • ネットワークメッセージである MsgType.Owner が、プレイヤーを追加したクライアントに送られる(そのクライアントだけに送られます)
  • 元々のクライアントがネットワークメッセージを受け取る
  • OnStartLocalPlayer() が元々のクライアントのプレイヤーインスタンスに呼び出され、isLocalPlayer が True に設定される

ここで留意すべきは、OnStartLocalPlayer() は OnStartClient() の後に呼び出される事です。なぜなら、プレイヤーオブジェクトが Spawn (生成)された後に所有者の メッセージがサーバーから届いたときのみ OnStartLocalPlayer が呼び出されるからです。したがって、isLocalPlayer は OnStartClient() では設定されていません。

OnStartLocalPlayer は「あなたの」プレイヤーに対してのみ呼び出されるので、ローカルプレイヤーだけに対して行われるべき初期化はここで実行されるのが理に適っています。これには入力処理の有効化、プレイヤーオブジェクトのカメラのトラッキングの有効化を含めることもあります。通常は、アクティブなカメラはローカルプレイヤーのみが持ちます。

クライアント権限を持つオブジェクトの生成

オブジェクトを生成し、そのオブジェクトに特定のクライアントの権限を割り当てることができます。NetworkServer.SpawnWithClientAuthority で行い、これには引数として権限を作成するクライアントの NetworkConnection が必要です。

これらのオブジェクトでは、権限を持つクライアント上では hasAuthority プロパティーが true となり、OnStartAuthority() が呼び出されます。またクライアントでは権限を持つオブジェクトのために Commands を発行することもできます。ほかのクライアント(とホスト)では、hasAuthority は false となります。

クライアント権限を持つオブジェクトの生成は、オブジェクトの NetworkIdentity に LocalPlayerAuthority を持つ必要があります。

例えば、プレイヤーの生成とオブジェクトの制御を可能にするコードは以下になります。

[Command]
void CmdSpawn()
{
    var go = (GameObject)Instantiate(
       otherPrefab, 
       transform.position + new Vector3(0,1,0), 
       Quaternion.identity);
       
    NetworkServer.SpawnWithClientAuthority(go, connectionToClient);
}
NetworkManager を使用する
カスタムの Spawn 関数