読者です 読者をやめる 読者になる 読者になる

tanaka's Programming Memo

プログラミングについてのメモ。

Unity5.1 ネットワークシステムのオブジェクト生成

前へ | 次へ

UNet Unity5.1からの新しいネットワークシステム
Unity5.1のネットワークマニュアル斜め読み(2)
UNet NetworkManagerの利用
Unity5.1 ネットワークシステムのオブジェクト生成
Unity5.1 ネットワークシステムの状態同期
Unity5.1 ネットワークシステムのRemote Actions
Unity5.1 ネットワークシステム PlayerObjects
Unity5.1 ネットワークシステム Object Visibility
Unity5.1 ネットワークシステム Network Messages
Unity5.1 ネットワークシステム Matchmaker
Unity5.1 ネットワークシステム Scene Objects
Unity5.1 ネットワークシステム:シングルプレイヤーゲームを多人数に対応させる
Unity5.1 ネットワークシステム Multiplayer Lobby
Unity5.1 ネットワークシステム Network Clients and Servers
Unity5.1 ネットワークリファレンス概要

以下の公式サイトの斜め読みのメモです。
Unity - マニュアル: Object Spawning

UnityではInstanciate()で新しいゲームオブジェクトを生成することを"Spawning"ともいいます。HLAPIネットワークにおける"Spawn"という言葉はより多くの意味を持ちます。サーバーが管理するHLAPIネットワークモデルで"Spawn"を行うと、ゲームオブジェクトがサーバー上で生成されると同時に、サーバーに接続されている全てのクライアント上にも同様にゲームオブジェクトが生成され、そのゲームオブジェクトはSpawning systemによって管理されるようになります。Spawning systemは、サーバー上でオブジェクトの状態が変化するとクライアントにそれを送信し、サーバー上でそのゲームオブジェクトが破棄されたらクライアントのものも破棄します。Spawnされたオブジェクトはサーバーで管理されます。そのため、新しいクライアントが後からゲームに参加した場合でもちゃんとこれまで生成されたオブジェクトが生成されます。これらのゲームオブジェクトは"netId"と呼ばれる一意の値を持ちます。サーバーとクライアントそれぞれに登録されている同じオブジェクトは同じ"netId"を持ちます。これはオブジェクトを指定してメッセージを送ったり、オブジェクトを特定するのに利用します。

NetworkIdentityコンポーネントを持ったオブジェクトがクライアント上でSpawnされると、サーバー上に現在の状態のオブジェクトが生成されます。適用される状態は、オブジェクトの位置情報(transform)、移動状態、そして同期データです。それらはオブジェクトが生成された時に常に設定されます。そのため、オブジェクトが本来とは違う位置に出現して、パケットで更新データが届いた時に本来の場所にワープするような不具合が回避できます。

これは一見便利そうですが、クライアント上でオブジェクトを生成するにはどうしたらよいのでしょうか?また、オブジェクトがSpawnされてから、他のクライアントがネットワークに参加する間に、Spawnされたオブジェクトの状態が変更されたらどうなるのでしょうか?新しいクライアントに登場するオブジェクトは、生成された時のものと状態が変更されたあとのものとどちらが適用されるのでしょうか。

クライアント上でオブジェクトをSpawnすると、まずはサーバー上で、NetworkServer.Spawn()に渡されたオブジェクトのプレハブからクライアントのオブジェクトが生成されます。NetworkIdentityのインスペクターのプレビューパネルで、NetworkIdentityのAsset IDが確認できます。この値でプレハブを特定してクライアントはオブジェクトが作成できるのです。この動作を効率的にするために、クライアントから作成されるアセットは、ClientScene.RegisterPrefab()関数を呼び出して、システムに登録する必要があります。

生成するプレハブの登録は、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);
    }
}

この例では、このスクリプトのインスペクターのalienPrefab欄にプレハブアセットをドラッグ&ドロップしている前提です。エイリアンのオブジェクトがサーバー上でSpawnされると、同じ種類のオブジェクトが接続されている全てのクライアント上に登場します。アセットの登録でシーンに読み込まれるので、実際にアセットを登場させる時には読み込み時間が発生することがなくなります。さらに効果的なのは動的なアセット生成です。ClientScene.RegisterSpawnHandler()という関数を使って、イベントハンドラにクライアント側の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欄に木のプレハブアセットの密集度を設定します。またtreeプレハブをSpawn可能なオブジェクトとしてNetworkManagerのUIか、あるいはClientScene.RegisterPrefab()関数を使って登録します。

このコードを実行すると、サーバーのnumLeavesと同じ値を持った木のオブジェクトがクライアント上に作成されます。

制限

  • NetworkIdentityは、Spawn対象のプレハブのルートとなるゲームオブジェクトに設定する必要があります
  • NetworkBehaviourスクリプトはNetworkIdentityと同じルートのゲームオブジェクトに設定する必要があります
  • NetworkIdentityがルートのゲームオブジェクトに設定されていないプレハブは、NetworkManagerに登録できません

オブジェクトが生成される流れ(Object Creation Flow)

オブジェクトが作成される流れは以下の通りです。

  • NetworkIdentityコンポーネントを追加したプレハブをspawn対象のプレハブとして登録
  • サーバー上でプレハブからゲームオブジェクトが生成される
  • ゲームコードが、生成されたオブジェクトに初期値を与える(ここで設定された3D的な物理挙動は、すぐには反映されません)
  • 生成したインスタンスを渡してNetworkServer.Spawn()関数を呼び出す
  • サーバー上のオブジェクトのインスタンスのSyncVarsの状態が、NetworkBehaviourコンポーネント上のOnSerialize()の呼び出しで収集される
  • MsgType.ObjectSpawnというネットワークメッセージが、SyncVarのデータと一緒に接続されている全てのクライアントに送信される
  • サーバー上のオブジェクトのインスタンスのOnStartServer()が呼ばれ、isServerプロパティがtrueに設定される
  • クライアントがObjectSpawnメッセージを受けとり、登録されているプレハブから新しいインスタンスを生成する
  • NetworkBehaviourコンポーネント上のDeSerialize()を呼ぶことで、クライアント上の新しいオブジェクトのインスタンスのSyncVarデータが更新される
  • 全てのクライアント上のインスタンスのOnStartClient()が呼ばれ、isClientプロパティがtrueに設定される
  • ゲームの進行によって変更されるSyncVarの値は自動的に全クライアントと同期される。これはゲームが終了するまで続く
  • サーバー上のインスタンスのNetworkServer.Destroy()が呼ばれる
  • MsgType.ObjectDestroyネットワークメッセージがクライアントに送信される
  • クライアント上のインスタンスのOnNetworkDestroy()が呼ばれ、インスタンスが破棄される

プレイヤーオブジェクト(Player Objects)

HLAPIネットワークにおけるプレイヤーオブジェクトはいくつか特殊な動きをします。NetworkManagerによるプレイヤーオブジェクトのSpawnの流れを記します。

  • NetworkIdentityコンポーネントを追加したプレハブをプレイヤープレハブとして登録する
  • クライアントがサーバーに接続
  • クライアントがAddPlayer()を呼び出して、MsgType.AddPlayerネットワークメッセージがサーバーに送信される
  • サーバーがメッセージを受信して、NetworkManager.OnServerAddPlayer()を呼ぶ
  • サーバー上のプレイヤープレハブからゲームオブジェクトが生成される
  • サーバー上で、新しいプレイヤーインスタンスを渡してNetworkManager.AddPlayerForConnection()を呼ぶ
  • プレイヤーのインスタンスが生成される。インスタンスは自動的に生成されるのでNetworkServer.Spawn()を呼ばないこと
  • MsgType.Ownerネットワークメッセージがプレイヤーを追加したクライアントに送信される。このメッセージはプレイヤーを生成したクライアントのみに送信される
  • プレイヤーを生成した元のクライアントがネットワークメッセージを受け取る
  • 生成元のクライアント上のプレイヤーのインスタンスのOnStartLocalPlayer()が呼び出され、isLocalPlayerプロパティがtrueに設定される

OnStartLocalPlayer()は、必ずOnStartClient()の後に呼び出されます。つまり、サーバーからプレイヤーオブジェクトの持ち主である通知がくるのはオブジェクトが生成された後ということになります。OnStartClient()内ではisLocalPlayerプロパティは設定されていません。

OnStartLocalPlayer()はプレイヤー本人のみに送信されるので、ローカルプレイヤー用の設定を行う場所として最適です。入力処理を有効にしたり、カメラが追従するプレイヤーを設定するのもこの場所が適しています。

Spawn関数のカスタマイズ(Custom Spawn Functions)

クライアント上のプレハブからオブジェクトを生成する基本動作はspawnハンドラー関数を使ってカスタマイズできます。クライアントオブジェクトをspawnしたり、削除する時の関数をClientScene.RegisterSpawnHandler()関数を使って登録できます。

ClientScene.RegisterSpawnHandler()関数に登録する関数のdelegate宣言は以下の通りです。公式サイトからの抜粋です。

// クライアント上でオブジェクトを生成する要求に対してのハンドル(Handles requests to spawn objects on the client)
public delegate GameObject SpawnDelegate(Vector3 position, NetworkHash128 assetId);

// クライアント上でオブジェクトを削除する要求のハンドル(Handles requests to unspawn objects on the client)
public delegate void UnSpawnDelegate(GameObject spawned);

spawn関数に渡されたassetIDは、プレハブのNetworkIdentity.assetIdから見つけることができ、自動的に配置されます。以下のようにカスタムassetIdを使ってオブジェクトをコードで作成することもできます。

// 新しい一意のassetId(ここではe2656f)を生成(generate a new unique assetId )
NetworkHash128 creatureAssetId = new NetworkHash.Parse("e2656f");

// assetIdのためのハンドラーを登録する(register handlers for assetId)
ClientScene.RegisterSpawnHandler(creatureAssetId, SpawnCreature, UnSpawnCreature);

// クリーチャーをspawnする。SpawnCreature関数がクライアント上で呼ばれる(spawn a creature - SpawnCreature will be called on client.)
NetworkServer.Spawn(gameObject, creatureAssetId);

ホスト上では、サーバー上にすでにオブジェクトが存在することになるので、ローカルクライアントのためのspawnは発生しません。そのため、spawnハンドラー関数は呼ばれることはありません。


前へ | 次へ