クライアント上でプレハブからゲームオブジェクトをスポーンするときに、スポーンハンドラー関数を使用してデフォルトの挙動をカスタマイズできます。スポーンハンドラー関数を使うと、ゲームオブジェクトの破棄だけでなく、スポーンの方法を完全に制御することができます。
ClientScene.RegisterSpawnHandler を使って、クライアントゲームオブジェクトをスポーン、および破棄する関数を登録します。サーバーは直接ゲームオブジェクトを作成し、この機能を通じてクライアント上にスポーンします。この関数は、ゲームブジェクトのアセット ID を取り、2 つの関数にデリゲートします。1 つはクライアント上でのゲームオブジェクトの生成を処理し、1 つはクライアント上でのゲームオブジェクトの破棄を扱います。アセット ID は動的か、(もしあるなら) スポーンしたいプレハブゲームオブジェクトのアセット ID にすることができます。
スポーン/アンスポーン (un-spawn) する関数は、ゲームオブジェクトのシグネチャを持つ必要があり、高レベルAPI で定義されています。
// クライアント上でオブジェクトをスポーンするリクエストを処理します
public delegate GameObject SpawnDelegate(Vector3 position, NetworkHash128 assetId);
// クライアント上でオブジェクトをアンスポーンするリクエストを処理します
public delegate void UnSpawnDelegate(GameObject spawned);
スポーン関数に渡すアセット ID は、プレハブの場合は NetworkIdentity.assetId を使って見つけることができます。これは、自動的に表示されます。動的なアセット ID は次のように処理します。
// 一意の新しい assetId を生成
NetworkHash128 creatureAssetId = NetworkHash128.Parse("e2656f");
// 新しい assetId のハンドラーを設定
ClientScene.RegisterSpawnHandler(creatureAssetId, SpawnCreature, UnSpawnCreature);
// 既存のプレハブ上で assetId を取得
NetworkHash128 coinAssetId = coinPrefab.GetComponent<NetworkIdentity>().assetId;
// カスタムでスポーンしたい既存のプレハブのハンドラーを設定
ClientScene.RegisterSpawnHandler(coinAssetId, SpawnCoin, UnSpawnCoin);
// コインをスポーン - SpawnCoin がクライアント上で呼び出されます
NetworkServer.Spawn(gameObject, coinAssetId);
スポーン関数自身は、デリゲートのシグネチャで実装されています。これはコインのスポーンを行う関数 (coin spawner) です。SpawnCreature は同じように見えますが、スポーンのロジックは異なります。
public GameObject SpawnCoin(Vector3 position, NetworkHash128 assetId)
{
return (GameObject)Instantiate(m_CoinPrefab, position, Quaternion.identity);
}
public void UnSpawnCoin(GameObject spawned)
{
Destroy(spawned);
}
カスタムのスポーン関数を使用するときに、ゲームオブジェクトを破棄せずにゲームオブジェクトのアンスポーン (スポーンの破棄) ができるのは便利なことがあります。これは、NetworkServer.UnSpawn を呼び出すことによって行います。この関数によってメッセージがクライアントに送信され、ゲームオブジェクトをアンスポーンし、それで、カスタムのアンスポーンの関数がクライアント上で呼び出されます。この関数が呼び出されたときにオブジェクトは破棄されません。
ホスト上では、ゲームオブジェクトはローカルクライアントのためにスポーンされません。なぜなら、すでにサーバー上に存在するからです。つまり、スポーンハンドラー関数は、ホスト上では一切呼び出されません。
ここでは、カスタムのスポーンハンドラーを持つ、とてもシンプルなゲームオブジェクトプールの設定例を紹介します。スポーン/アンスポーンしてから、ゲームオブジェクトをプールに出し入れすることができます。
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
public class SpawnManager : MonoBehaviour
{
public int m_ObjectPoolSize = 5;
public GameObject m_Prefab;
public GameObject[] m_Pool;
public NetworkHash128 assetId { get; set; }
public delegate GameObject SpawnDelegate(Vector3 position, NetworkHash128 assetId);
public delegate void UnSpawnDelegate(GameObject spawned);
void Start()
{
assetId = m_Prefab.GetComponent<NetworkIdentity> ().assetId;
m_Pool = new GameObject[m_ObjectPoolSize];
for (int i = 0; i < m_ObjectPoolSize; ++i)
{
m_Pool[i] = (GameObject)Instantiate(m_Prefab, Vector3.zero, Quaternion.identity);
m_Pool[i].name = "PoolObject" + i;
m_Pool[i].SetActive(false);
}
ClientScene.RegisterSpawnHandler(assetId, SpawnObject, UnSpawnObject);
}
public GameObject GetFromPool(Vector3 position)
{
foreach (var obj in m_Pool)
{
if (!obj.activeInHierarchy)
{
Debug.Log("Activating GameObject " + obj.name + " at " + position);
obj.transform.position = position;
obj.SetActive (true);
return obj;
}
}
Debug.LogError ("Could not grab GameObject from pool, nothing available");
return null;
}
public GameObject SpawnObject(Vector3 position, NetworkHash128 assetId)
{
return GetFromPool(position);
}
public void UnSpawnObject(GameObject spawned)
{
Debug.Log ("Re-pooling GameObject " + spawned.name);
spawned.SetActive (false);
}
}
このマネージャーを使うために、新しい空のゲームオブジェクトを作成し「SpawnManager」と名付けます。SpawnManager という新しいスクリプトを作成し、上のコードサンプルにコピーします。そして、それを新しい SpawnManager ゲームオブジェクトにアタッチします。次に、複数回スポーンしたいプレハブを Prefab フィールドへドラッグし、 Object Pool Size を設定します (デフォルトは 5)。
最後に、プレイヤーの移動に使用するスクリプトで SpawnManager への参照を設定します。
SpawnManager spawnManager;
void Start()
{
spawnManager = GameObject.Find("SpawnManager").GetComponent<SpawnManager> ();
}
プレイヤーのロジックは、例えば、以下のようなものを含んでいます。ここでは、コインを移動し発射します。
void Update()
{
if (!isLocalPlayer)
return;
var x = Input.GetAxis("Horizontal")*0.1f;
var z = Input.GetAxis("Vertical")*0.1f;
transform.Translate(x, 0, z);
if (Input.GetKeyDown(KeyCode.Space))
{
// コマンド関数はクライアント上で呼び出されますがサーバー上で発生します
CmdFire();
}
}
プレイヤーの発射ロジックでは、オブジェクトプールを使用するとよいでしょう。
[Command]
void CmdFire()
{
// サーバー上でコインを設定
var coin = spawnManager.GetFromPool(transform.position + transform.forward);
coin.GetComponent<Rigidbody>().velocity = transform.forward*4;
// クライアント上でコインをスポーン。カスタムのスポーンハンドラーが呼び出されます。
NetworkServer.Spawn(coin, spawnManager.assetId);
// コインがサーバーで破棄されるとき、クライアント側でも自動的に破棄されます。
StartCoroutine (Destroy (coin, 2.0f));
}
public IEnumerator Destroy(GameObject go, float timer)
{
yield return new WaitForSeconds (timer);
spawnManager.UnSpawnObject(go);
NetworkServer.UnSpawn(go);
}
自動的な破棄によって、ゲームオブジェクトがプールに戻され、再度コインを発射するときに再使用する方法を表しています。