유니티 Netcode for GameObject, Object Spawning

Object Spawning

유니티에서는 일반적으로 Instantiate 함수를 통해 게임오브젝트를 만들곤 합니다

Instantiate함수를 이용하여 만든 게임오브젝트를 동기화 하기 위해서는 Spawn()이라는 함수를 이용하면 됩니다!

 

Network Prefab에는 게임오브젝트에 NetworkObject 컴포넌트가 붙어 있어야 합니다.

주의할 점은, 둘 이상의 NetworkObject 컴포넌트가 붙어 있는 프리팹은 추가할 수 없습니다!

 

NetworkObject컴포넌트를 가지면 NetworkObject.NetworkObjectId를 이용할 수 있습니다!

 

사용법은 아래와 같습니다!

NetworkObject를 가지는 프리팹을
NetworkManager의 NetworkPrefabs에 할당하면 됩니다


Spawning a Network Prefab (Overview)

Netcode는 서버권위적이므로 Network 프리팹을 소환하기 위해서는 서버 또는 호스트에서 이루어져야 합니다!

GameObject go = Instantiate(myPrefab, Vector3.zero, Quaternion.identity);
go.GetComponent<NetworkObject>().Spawn();

Spawn됨을 확인할 수 있다

단, 주의할 점은 NetworkManager에 NetworkPrefab에 등록해주어야합니다!

 
기본적으로 Network오브젝트를 생성하면 서버가 소유권을 가집니다!
public void Spawn(bool destroyWithScene = true);
NetworkObject.Spawn에는 하나의 옵션을 추가할 수 있습니다
destroyWithScene의 옵션은 true가 default상태이며, false로 설정할 경우 DontDestroyOnLoad와 같이 씬이 바뀌어도 파괴되지 않습니다
 
코드를 사용하여 역동적으로 NetworkObject를 생성할 수 있습니다!

다음 예시는, non-pooled방식을 이용하여 NetworkObject를 역동적으로 생성하는 방법과 Despawn하는 방법입니다!

    public class NonPooledDynamicSpawner : NetworkBehaviour
    {
        public GameObject PrefabToSpawn;
        public bool DestroyWithSpawner;        
        private GameObject m_PrefabInstance;
        private NetworkObject m_SpawnedNetworkObject;

        public override void OnNetworkSpawn()
        {
            // Only the server spawns, clients will disable this component on their side
            enabled = IsServer;            
            if (!enabled || PrefabToSpawn == null)
            {
                return;
            }
            // Instantiate the GameObject Instance
            m_PrefabInstance = Instantiate(PrefabToSpawn);
            
            // Optional, this example applies the spawner's position and rotation to the new instance
            m_PrefabInstance.transform.position = transform.position;
            m_PrefabInstance.transform.rotation = transform.rotation;
            
            // Get the instance's NetworkObject and Spawn
            m_SpawnedNetworkObject = m_PrefabInstance.GetComponent<NetworkObject>();
            m_SpawnedNetworkObject.Spawn();
        }

        public override void OnNetworkDespawn()
        {
            if (IsServer && DestroyWithSpawner && m_SpawnedNetworkObject != null && m_SpawnedNetworkObject.IsSpawned)
            {
                m_SpawnedNetworkObject.Despawn();
            }
            base.OnNetworkDespawn();
        }
    }

 


Destroying / Despawning

기본적으로, 서버나 호스트에서 Network Prefab이 파괴되면 모든 클라이언트에서 자동으로 파괴됩니다!

 

클라이언트측에서 연결이 끊겼을 때, 클라이언트측의 Network Object 프리팹이 파괴되지 않길 바란다면

m_SpawnedNetworkObject.DontDestroyWithOwner = true;
m_SpawnedNetworkObject.Despawn();

 

위 코드와 같이, m_SpawnedNetworkObject.DontDestroyWithOwner = true; 를 설정하면 됩니다!

또는,

컴포넌트에서 세팅할 수 있습니다!

 


Despawning

서버만이 NetworkObject를 despawn할 수 있습니다

서버측에서 NetworkObject를 despawn/destroy하게 되면 클라이언트측에서도 똑같이 despawn/destroy됩니다

 

클라이언트측에서는 절대 Destroy를 호출할 수 없습니다

만약 클라이언트가 우선이 되는 모델을 사용하고 싶다면, ServerRPC를 호출하여 서버측에 despawning됨을 연기(속인다는 뜻)하도록 하면 됩니다!

 

Pooled Dynamic Spawning

Pooled Dynamic Spawning은 Netcode Object가 Despawn될 때 파괴하지 않습니다

즉, Despawn될 때 Disabled상태로 바꿔버리는 것입니다!

 

NGO에서는 INetworkPrefabInstanceHandler 인터페이스를 통해 많은 Netcode Object를 Instantiation하고 파할 수 있습니다!

 

다음 코드는 PooledDynamicSpawn의 예시입니다!

public class SinglePooledDynamicSpawner : NetworkBehaviour, INetworkPrefabInstanceHandler
{
    public GameObject PrefabToSpawn;
    public bool SpawnPrefabAutomatically;

    private GameObject m_PrefabInstance;
    private NetworkObject m_SpawnedNetworkObject;


    private void Start()
    {
        // Instantiate our instance when we start (for both clients and server)
        m_PrefabInstance = Instantiate(PrefabToSpawn);

        // Get the NetworkObject component assigned to the Prefab instance
        m_SpawnedNetworkObject = m_PrefabInstance.GetComponent<NetworkObject>();

        // Set it to be inactive
        m_PrefabInstance.SetActive(false);
    }

    private IEnumerator DespawnTimer()
    {
        yield return new WaitForSeconds(2);
        m_SpawnedNetworkObject.Despawn();
        StartCoroutine(SpawnTimer());
        yield break;
    }

    private IEnumerator SpawnTimer()
    {
        yield return new WaitForSeconds(2);
        SpawnInstance();
        yield break;
    }

    /// <summary>
    /// Invoked only on clients and not server or host
    /// INetworkPrefabInstanceHandler.Instantiate implementation
    /// Called when Netcode for GameObjects need an instance to be spawned
    /// </summary>
    public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation)
    {
        m_PrefabInstance.SetActive(true);
        m_PrefabInstance.transform.position = transform.position;
        m_PrefabInstance.transform.rotation = transform.rotation;
        return m_SpawnedNetworkObject;
    }

    /// <summary>
    /// Client and Server side
    /// INetworkPrefabInstanceHandler.Destroy implementation
    /// </summary>
    public void Destroy(NetworkObject networkObject)
    {
        m_PrefabInstance.SetActive(false);
    }

    public void SpawnInstance()
    {
        if (!IsServer)
        {
            return;
        }

        if (m_PrefabInstance != null && m_SpawnedNetworkObject != null && !m_SpawnedNetworkObject.IsSpawned)
        {
            m_PrefabInstance.SetActive(true);
            m_SpawnedNetworkObject.Spawn();
            StartCoroutine(DespawnTimer());
        }
    }

    public override void OnNetworkSpawn()
    {
        // We register our network Prefab and this NetworkBehaviour that implements the
        // INetworkPrefabInstanceHandler interface with the Prefab handler
        NetworkManager.PrefabHandler.AddHandler(PrefabToSpawn, this);

        if (!IsServer || !SpawnPrefabAutomatically)
        {
            return;
        }

        if (SpawnPrefabAutomatically)
        {
            SpawnInstance();
        }
    }

    public override void OnNetworkDespawn()
    {
        if (m_SpawnedNetworkObject != null && m_SpawnedNetworkObject.IsSpawned)
        {
            m_SpawnedNetworkObject.Despawn();
        }
        base.OnNetworkDespawn();
    }

    public override void OnDestroy()
    {
        // This example destroys the
        if (m_PrefabInstance != null)
        {
            // Always deregister the prefab
            NetworkManager.Singleton.PrefabHandler.RemoveHandler(PrefabToSpawn);
            Destroy(m_PrefabInstance);
        }
        base.OnDestroy();
    }
}

Spanw Prefab Automatically를 체크하면 Host가 접속함과 동시에 Prefab To Spawn에 등록한 Network Object를 소환합니다

2초 후에는 SetActive를 False로 만들고 2초 후에 다시 true로 계속 반복합니다!

즉, 기본적인 Pooling System과 성격이 비슷합니다!

2022.09.04 - [나만의 꿀팁] - Unity Pooling System (최적화 구웃!)

 

Unity Pooling System (최적화 구웃!)

서론 없이 바로 코드부터 투척!! 먼저 TestPooling을 싱글톤으로 만들어버리자! 그리고 무수히 많이 생성될 녀석 -> poolPrefab을 만들자! 그리고 이녀석들은 Queue로 관리된다! 선입선출? 맞나? FIFO이건

wlsdn629.tistory.com

 


In-Scene Placed NetworkObject

씬에 이미 놓여져 있는 NetworkObject들은 자동으로 복제됩니다!

따라서,추가 작업을 할 필요는 없습니다!

 

일반적으로, 씬에 놓여 있는 NetworkObject들을 동기화하는데에는 2가지 방법이 있습니다

  1. Soft Synchronization (Scene Management 사용할 때)
  2. Prefab Synchronization (Scene Management 사용하지 않을 )

 

Scene Management를 사용하면 얻는 이점은 씬에 놓여 있는 NetworkObject들을 매번 NetworkManager안에 NetworkObject로 등록하지 않아도 됩니다!