クライアント上でスポーンされるオブジェクトをプレハブから生成するデフォルトの挙動は、スポーンハンドラー関数を使ってカスタマイズできます。この方法により、スポーンするオブジェクトをスポーンしないオブジェクトと同様に、完全に制御できます。クライアントのスポーンする/しないオブジェクトに、 ClientScene.RegisterSpawnHandler を使って、関数を登録します。この機能を通じて、サーバーは直接オブジェクトを生成し、クライアント上にスポーンします。この関数は、オブジェクトのアセット ID を取得し、 2 つの関数に委譲します。 1 つはクライアント上でのオブジェクトの生成を扱い、 1 つはクライアント上でのオブジェクトの削除を扱います。アセット ID は動的なものにするか、(もしあるなら)スポーンさせたいプレハブオブジェクトから選んだアセット ID にすることができます。
スポーン/アンスポーン は、オブジェクトのシグネチャを持つ必要があり、高レベルAPIで定義されています。
// 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);
アセットIDをスポーン関数に渡し、複数のプレハブの中から NetworkIdentity.assetId を使って自動で対象のプレハブを取得することができます。動的なアセットIDは次のように処理します。
// generate a new unique assetId
NetworkHash128 creatureAssetId = NetworkHash128.Parse("e2656f");
// register handlers for the new assetId
ClientScene.RegisterSpawnHandler(creatureAssetId, SpawnCreature, UnSpawnCreature);
// get assetId on an existing prefab
NetworkHash128 bulletAssetId = bulletPrefab.GetComponent<NetworkIdentity>().assetId;
// register handlers for an existing prefab you'd like to custom spawn
ClientScene.RegisterSpawnHandler(bulletAssetId, SpawnBullet, UnSpawnBullet);
// spawn a bullet - SpawnBullet will be called on client.
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))
{
// Command function is called on the client, but invoked on the server
CmdFire();
}
}
[Command]
void CmdFire()
{
// Set up bullet on server
var bullet = spawnManager.GetFromPool(transform.position + transform.forward);
bullet.GetComponent<Rigidbody>().velocity = transform.forward*4;
// spawn bullet on client, custom spawn handler will be called
NetworkServer.Spawn(bullet, spawnManager.assetId);
// when the bullet is destroyed on the server it wil automatically be destroyed on clients
StartCoroutine (Destroy (bullet, 2.0f));
}
public IEnumerator Destroy(GameObject go, float timer)
{
yield return new WaitForSeconds (timer);
spawnManager.UnSpawnObject(go);
NetworkServer.UnSpawn(go);
}
上記のコードは、オブジェクトが破棄されるタイミングで破棄をキャンセルし、再使用するためにオブジェクトプールが行われます。