클라이언트의 프리팹에서 스폰된 오브젝트를 생성하는 디폴트 동작은 스폰 핸들러 함수를 사용하여 커스텀화할 수 있습니다. 이렇게 하면 오브젝트를 스폰하고 스폰 해제하는 방법을 완벽히 제어할 수 있습니다. 클라이언트 오브젝트를 스폰하고 스폰 해제하는 함수는 ClientScene.RegisterSpawnHandler에서 등록할 수 있습니다. 서버는 오브젝트를 직접 생성하여 이 기능을 통해 클라이언트에 이를 스폰합니다. 이 기능은 오브젝트의 에셋 ID와 두 개의 함수 델리게이트를 사용합니다. 여기서 에셋 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를 호출하면 됩니다. 호출하게 되면 클라이언트에 메시지가 보내져 오브젝트를 언스폰하도록 하므로 클라이언트에서 언스폰 함수가 호출됩니다. 이 함수가 호출되더라도 오브젝트가 제거되지는 않습니다.
호스트의 경우 서버에 이미 존재하므로 로컬 클라이언트에 대해 오브젝트가 스폰되지 않는다는 점을 상기해야 합니다. 따라서 스폰 핸들러 함수는 호출되지 않습니다.
아래는 커스텀 스폰 핸들러를 사용하여 아주 간단한 오브젝트 풀링 시스템을 만드는 예시입니다. 스포닝이나 언스포닝을 하면 오브젝트가 풀에 추가하거나 풀에서 삭제됩니다.
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);
}
자동 제거 과정을 통해 오브젝트가 어떻게 풀로 돌아오고 발사하는 경우 다시 사용되는지 볼 수 있습니다.