クライアント上でスポーンされるオブジェクトをプレハブから生成するデフォルトの挙動は、スポーンハンドラー関数を使ってカスタマイズできます。この方法により、スポーンするオブジェクトをスポーンしないオブジェクトと同様に、完全に制御できます。クライアントのスポーンする/しないオブジェクトに、 ClientScene.RegisterSpawnHandler を使って、関数を登録します。この機能を通じて、サーバーは直接オブジェクトを生成し、クライアント上にスポーンします。この関数は、オブジェクトのアセット ID を取得し、 2 つの関数に委譲します。 1 つはクライアント上でのオブジェクトの生成を扱い、 1 つはクライアント上でのオブジェクトの削除を扱います。アセット ID は動的なものにするか、(もしあるなら)スポーンさせたいプレハブオブジェクトから選んだアセット ID にすることができます。
スポーン/アンスポーン は、オブジェクトのシグネチャを持つ必要があり、高レベルAPIで定義されています。
// クライアント上でオブジェクトを生成する (Spawn) ために、リクエストを処理します
public delegate GameObject SpawnDelegate(Vector3 position, NetworkHash128 assetId);
// クライアント上でオブジェクトを破棄する (unspawn) ために、リクエストを処理します
public delegate void UnSpawnDelegate(GameObject spawned);
アセットIDをスポーン関数に渡し、複数のプレハブの中から NetworkIdentity.assetId を使って自動で対象のプレハブを取得することができます。動的なアセットIDは次のように処理します。
// 一意の新しい assetId を生成
NetworkHash128 creatureAssetId = NetworkHash128.Parse("e2656f");
// 新しい assetId のハンドラーを設定
ClientScene.RegisterSpawnHandler(creatureAssetId, SpawnCreature, UnSpawnCreature);
// 既存のプレハブ上で assetId を取得
NetworkHash128 bulletAssetId = bulletPrefab.GetComponent<NetworkIdentity>().assetId;
// カスタム生成したい既存のプレハブのハンドラーを設定
ClientScene.RegisterSpawnHandler(bulletAssetId, SpawnBullet, UnSpawnBullet);
// 弾丸を生成 - SpawnBullet はクライアント上で呼び出されます
NetworkServer.Spawn(gameObject, creatureAssetId);
スポーン関数自身は、デリゲートのシグネチャで実装されています。ここでは、弾の生成について記述し、 SpawnCreature は同じように見えますが、生成ロジックは異なります。
public GameObject SpawnBullet(Vector3 position, NetworkHash128 assetId)
{
return (GameObject)Instantiate(m_BulletPrefab, position, Quaternion.identity);
}
public void UnSpawnBullet(GameObject spawned)
{
Destroy(spawned);
}
カスタムのスポーン関数を使用するときに、オブジェクトを破棄せずにオブジェクトのアンスポーンができるのはときどき便利なことがあります。これは、NetworkServer.UnSpawn を呼び出すことでアンスポーンが行えます。この関数はクライアントに送信され、クライアントのアンスポーンするオブジェクトへとメッセージを送信します。そしてアンスポーンの関数はクライアント上で呼び出されます。関数が呼び出されたときにオブジェクトは破棄されません。
(注)ホスト上では、オブジェクトはローカルクライアント用に Spawn されません。すでにサーバー上に存在するからです。したがって、Spawn ハンドラー関数は一切呼び出されません。
ここでは、カスタムのスポーンハンドラーとして、とてもシンプルなオブジェクトプールを実装する例を紹介します。スポーンした後、アンスポーンするだけで、プールにオブジェクトを出し入れすることができます。
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 object " + obj.name + " at " + position);
obj.transform.position = position;
obj.SetActive (true);
return obj;
}
}
Debug.LogError ("Could not grab object 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 object " + spawned.name);
spawned.SetActive (false);
}
}
このマネージャーを使用するには、
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 bullet = spawnManager.GetFromPool(transform.position + transform.forward);
bullet.GetComponent<Rigidbody>().velocity = transform.forward*4;
// クライアントで弾丸を生成。カスタムの spawn ハンドラーが呼び出されます。
NetworkServer.Spawn(bullet, spawnManager.assetId);
// 弾丸がサーバーで破棄されるとき、クライアント側でも自動的に破棄されます。
StartCoroutine (Destroy (bullet, 2.0f));
}
public IEnumerator Destroy(GameObject go, float timer)
{
yield return new WaitForSeconds (timer);
spawnManager.UnSpawnObject(go);
NetworkServer.UnSpawn(go);
}
上記のコードは、オブジェクトが破棄されるタイミングで破棄をキャンセルし、再使用するためにオブジェクトプールが行われます。