오브젝트 스포닝
상태 동기화(State Synchronization)

커스텀 스폰 함수

클라이언트의 프리팹에서 스폰된 오브젝트를 생성하는 디폴트 동작은 스폰 핸들러 함수를 사용하여 커스텀화할 수 있습니다. 이렇게 하면 오브젝트를 스폰하고 스폰 해제하는 방법을 완벽히 제어할 수 있습니다. 클라이언트 오브젝트를 스폰하고 스폰 해제하는 함수는 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를 생성하고, 새로운 SpawnManager 오브젝트에 추가해야 합니다.
  • 여러번 스폰하고자 하는 프리팹을 프리팹 필드로 드래그한 후 크기를 설정해야 합니다. 디폴트는 5입니다.
  • 플레이어 이동 스크립트에서 스폰 관리자에 대한 참조를 설정합니다.
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 is automatically 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);
}

자동 제거 과정을 통해 오브젝트가 어떻게 풀로 돌아오고 발사하는 경우 다시 사용되는지 볼 수 있습니다.

오브젝트 스포닝
상태 동기화(State Synchronization)