커스텀 플레이어 스포닝
커스텀 스폰 함수

게임 오브젝트 스폰

참고: UNet은 지원이 중단되었으며 향후 Unity에서 삭제될 예정입니다. 현재 새로운 시스템이 개발 중입니다. 자세한 내용과 다음 단계는 이 블로그 포스트FAQ를 참조하십시오.

Unity에서는 일반적으로 Instantiate()를 사용하여 새 게임 오브젝트를 “스폰”(즉, 생성)합니다. 하지만 멀티플레이어 고수준 API에서 “스폰”은 좀 더 세분화됩니다. HLAPI의 서버 권한 HLAPI 모델에서는 서버에 게임 오브젝트를 “스폰”한다는 의미는 게임 오브젝트가 서버와 연결된 클라이언트에 생성되고 스폰 시스템에 의해 관리된다는 의미입니다.

이 시스템을 통해 게임 오브젝트가 스폰되면, 서버의 게임 오브젝트가 변경될 때마다 상태 업데이트가 클라이언트에 전송됩니다. Unity가 서버의 게임 오브젝트를 삭제하면 클라이언트에서도 삭제됩니다. 서버는 다른 모든 네트워크로 연결된 게임 오브젝트와 함께 스폰된 게임 오브젝트를 관리합니다. 따라서 나중에 다른 클라이언트가 게임에 참가하는 경우 서버는 해당 클라이언트에 게임 오브젝트를 스폰할 수 있습니다. 이렇게 스폰된 게임 오브젝트는 “netId”라고 불리는 고유한 네트워크 인스턴스 ID를 가집니다. 이 ID는 각 게임 오브젝트의 서버와 클라이언트에서 동일합니다. 이러한 고유 네트워크 인스턴스 ID를 통해 네트워크에서 설정된 메시지를 게임 오브젝트로 라우팅하고 게임 오브젝트를 식별합니다.

서버가 Network Identity** 컴포넌트를 사용하여 게임 오브젝트를 스폰하면 클라이언트에 스폰된 게임 오브젝트도 동일한 “상태”가 됩니다. 즉, 서버의 게임 오브젝트와 동일하며. 동일한 트랜스폼, 이동 상태, 그리고 (NetworkTransformSyncVars가 사용된 경우) 동기화된 변수를 가집니다. 따라서 Unity가 게임 오브젝트를 만들면 클라이언트 게임 오브젝트는 항상 최신 상태를 유지합니다. 이를 통해 잘못된 시작 위치에 게임 오브젝트를 스폰한 후 상태 업데이트가 도착할 때 올바른 위치에 다시 나타나는 문제 등을 피할 수 있습니다.

네트워크 관리자는 등록된 프리팹의 게임 오브젝트만 스폰하고 동기화할 수 있으므로, 게임 도중 스폰하고 싶은 특정 게임 오브젝트 프리팹이 있으면 네트워크 관리자에 등록해야 합니다. 네트워크 관리자는 Network Identity 컴포넌트가 연결되어 있는 게임 오브젝트 프리팹만 수락할 수 있습니다. 따라서 프리팹을 네트워크 관리자에 등록하기 전에 Network Identity 컴포넌트를 추가해야 합니다.

에디터에서 프리팹을 네트워크 관리자에 등록하려면 네트워크 관리자 게임 오브젝트를 선택한 후 인스펙터에서 Network Manager 컴포넌트로 이동합니다. Spawn Info 옆에 있는 삼각형을 클릭하고, Registered Spawnable Prefabs에서 더하기(+) 버튼을 클릭합니다. 프리팹을 빈 필드에 드래그 앤 드롭하여 리스트에 할당합니다.

Spawn Info* 폴드아웃이 확장된 네트워크 관리자 인스펙터. Registered Spawnable Prefabs 에 있는 세 개의 프리팹을 보여줌
Spawn Info* 폴드아웃이 확장된 네트워크 관리자 인스펙터. Registered Spawnable Prefabs 에 있는 세 개의 프리팹을 보여줌

네트워크 관리자를 사용하지 않는 스포닝

고급 사용자의 경우 NetworkManager 컴포넌트를 사용하지 않고 프리팹을 등록하고 게임 오브젝트를 스폰할 수도 있습니다.

네트워크 관리자를 사용하지 않고 게임 오브젝트를 스폰하려면 스크립트를 통해 프리팹 등록을 직접 처리하십시오. ClientScene.RegisterPrefab 메서드를 사용하여 네트워크 관리자에 프리팹을 등록할 수 있습니다.

예제: MyNetworkManager


using UnityEngine;
using UnityEngine.Networking;

public class MyNetworkManager : MonoBehaviour 
{
    public GameObject treePrefab;
    NetworkClient myClient;

    // Create a client and connect to the server port
    public void ClientConnect() {
        ClientScene.RegisterPrefab(treePrefab);
        myClient = new NetworkClient();
        myClient.RegisterHandler(MsgType.Connect, OnClientConnect);
        myClient.Connect("127.0.0.1", 4444);
    }

    void OnClientConnect(NetworkMessage msg) {
        Debug.Log("Connected to server: " + msg.conn);
    }
}

이 예제에서는 네트워크 관리자로 동작할 빈 게임 오브젝트를 생성한 후 위의 MyNetworkManager 스크립트를 생성하여 해당 게임 오브젝트에 연결합니다. Network Identity 컴포넌트가 연결된 프리팹을 생성하여 인스펙터의 MyNetworkManager 컴포넌트에 있는 treePrefab 슬롯에 끌어다 놓으십시오. 이렇게 하면 서버가 나무 게임 오브젝트를 스폰할 때 클라이언트에도 같은 종류의 게임 오브젝트가 생성됩니다.

프리팹을 등록하면 에셋이 씬에 로드되므로 에셋 생성을 위한 지연 또는 로딩 시간이 필요하지 않습니다.

하지만 스크립트가 동작하려면 서버에 대한 코드도 추가해야 합니다. 다음을 MyNetworkManager 스크립트에 추가하십시오.

public void ServerListen() {
    NetworkServer.RegisterHandler(MsgType.Connect, OnServerConnect);
    NetworkServer.RegisterHandler(MsgType.Ready, OnClientReady);
    if (NetworkServer.Listen(4444))
        Debug.Log("Server started listening on port 4444");
}

// When client is ready spawn a few trees
void OnClientReady(NetworkMessage msg) {
    Debug.Log("Client is ready to start: " + msg.conn);
    NetworkServer.SetClientReady(msg.conn);
    SpawnTrees();
}

void SpawnTrees() {
    int x = 0;
    for (int i = 0; i < 5; ++i) {
        var treeGo = Instantiate(treePrefab, new Vector3(x++, 0, 0), Quaternion.identity);
        NetworkServer.Spawn(treeGo);
    }
}

void OnServerConnect(NetworkMessage msg) {
    Debug.Log("New client connected: " + msg.conn);
}

서버는 어떤 게임 오브젝트가 스폰되는지 알고 에셋 ID가 스폰 메시지로 전송되기 때문에 아무것도 등록할 필요가 없습니다. 클라이언트는 게임 오브젝트를 검색하여 클라이언트에 등록할 수 있어야 합니다.

고유 네트워크 관리자를 작성할 때는 서버에서 스폰 커맨드를 호출하기 전에 클라이언트가 상태 업데이트를 받을 준비가 되어 있어야 합니다. 그렇지 않으면 상태 업데이트가 전송되지 않습니다. Unity의 빌트인 Network Manager 컴포넌트를 사용하면 이 작업이 자동으로 수행됩니다.

오브젝트 풀 또는 동적으로 생성된 에셋 등과 같은 고급 사용법의 경우 ClientScene.RegisterSpawnHandler 메서드를 사용하십시오. 이렇게 하면 콜백 함수가 클라이언트 쪽 스포닝을 위해 등록됩니다. 이에 관한 예제는 커스텀 스폰 함수에 대한 문서를 참조하십시오.

게임 오브젝트에 동기화된 변수 같은 네트워크 상태가 있는 경우 해당 상태는 스폰 메시지와 동기화됩니다. 다음 예제에서는 이 스크립트가 나무 프리팹에 연결되어 있습니다.

using UnityEngine;
using UnityEngine.Networking;
class Tree : NetworkBehaviour {
    [SyncVar]
    public int numLeaves;
    public override void OnStartClient() {
        Debug.Log("Tree spawned with leaf count " + numLeaves);
    }
}

이 스크립트를 연결하면 numLeaves 변수를 수정하고 SpawnTrees 함수를 수정하여 클라이언트에 정확하게 반영되는지 확인할 수 있습니다.

void SpawnTrees() {
    int x = 0;
    for (int i = 0; i < 5; ++i) {
        var treeGo = Instantiate(treePrefab, new Vector3(x++, 0, 0), Quaternion.identity);
        var tree = treeGo.GetComponent<Tree>();
        tree.numLeaves = Random.Range(10,200);
        Debug.Log("Spawning leaf with leaf count " + tree.numLeaves);
        NetworkServer.Spawn(treeGo);
    }
}

실제 동작 방식을 보려면 Tree 스크립트를 앞서 생성한 treePrefab 스크립트에 연결하십시오.

제약

  • NetworkIdentity는 스폰 가능한 프리팹의 루트 게임 오브젝트에 있어야 합니다. 그러지 않으면 네트워크 관리자가 프리팹을 등록할 수 없습니다.

  • NetworkBehaviour 스크립트는 NetworkIdentity와 동일한 게임 오브젝트에 있어야 하며, 해당 오브젝트의 자식 오브젝트에 있어서는 안됩니다.

게임 오브젝트 생성 플로

게임 오브젝트를 스폰하기 위한 내부 작업 과정은 아래와 같습니다.

  • Network Identity 컴포넌트가 있는 프리팹이 스폰 가능한 오브젝트로 등록됩니다.

  • 게임 오브젝트가 서버의 프리팹에서 인스턴스화됩니다.

  • 게임 코드가 인스턴스의 초기 값을 설정합니다. 여기서 적용된 3D 물리 힘은 즉시 반영되지 않습니다.

  • NetworkServer.Spawn()이 인스턴스와 호출됩니다.

  • Network Behaviour 컴포넌트의 OnSerialize() 호출에 의해 서버에 있는 인스턴스의 SyncVars 상태가 수집됩니다.

  • 연결된 클라이언트에게 MsgType.ObjectSpawn 타입 네트워크 메시지가 SyncVar 데이터를 포함하여 전송됩니다.

  • OnStartServer()가 서버의 해당 인스턴스에서 호출되고, isServertrue로 설정됩니다.

  • 클라이언트는 ObjectSpawn 메시지를 수신하고 등록된 프리팹에서 새로운 인스턴스를 생성합니다.

  • SyncVar 데이터는 Network Behaviour 컴포넌트에서 OnDeserialize()를 호출하는 방법으로 클라이언트의 새로운 인스턴스에 적용됩니다.

  • 각 클라이언트의 인스턴스에서 OnStartClient()가 호출되고, isClienttrue로 설정됩니다.

  • 계속 게임플레이가 진행되며, SyncVar 값이 변하면 자동으로 클라이언트에 동기화됩니다. 이 과정은 게임이 끝날 때까지 계속됩니다.

  • 서버의 인스턴스에서 NetworkServer.Destroy()가 호출됩니다.

  • MsgType.ObjectDestroy 타입 네트워크 메시지가 각 클라이언트로 전송됩니다.

  • 클라이언트의 인스턴스에서 OnNetworkDestroy()가 호출된 후 인스턴스가 제거됩니다.

플레이어 게임 오브젝트

HLAPI에서 플레이어 게임 오브젝트는 비플레이어 게임 오브젝트와 약간 다르게 동작합니다. 네트워크 관리자에서 플레이어 게임 오브젝트의 생성 플로는 다음과 같습니다.

  • NetworkIdentity가 있는 프리팹이 PlayerPrefab으로 등록됩니다.

  • 클라이언트가 서버에 연결됩니다.

  • 클라이언트는 AddPlayer()를 호출하고 MsgType.AddPlayer 타입 네트워크 메시지가 서버에 전송됩니다.

  • 서버는 메시지를 수신하고 NetworkManager.OnServerAddPlayer()를 호출합니다.

  • 게임 오브젝트가 서버의 PlayerPrefab에서 인스턴스화됩니다.

  • 서버의 새 플레이어 인스턴스와 함께 NetworkManager.AddPlayerForConnection()이 호출됩니다.

  • 플레이어 인스턴스가 스폰됩니다. 플레이어 인스턴스를 위해 NetworkServer.Spawn()을 호출할 필요가 없습니다. 일반 스폰에서와 동일하게, 스폰 메시지가 모든 클라이언트에 전송됩니다.

  • 플레이어를 추가한 클라이언트에 대해서만 MsgType.Owner 타입 네트워크 메시지가 전송됩니다.

  • 기존 클라이언트가 네트워크 메시지를 수신합니다.

  • 기존 클라이언트의 플레이어 인스턴스에서 OnStartLocalPlayer()가 호출되고, isLocalPlayer가 true로 설정됩니다.

플레이어 게임 오브젝트가 스폰된 이후에 소유권 메시지가 서버에서 전달되는 시점에서만 OnStartLocalPlayer()가 발생하므로, OnStartClient() 이후에만 호출된다는 점을 상기해야 합니다. 따라서 isLocalPlayerOnStartClient()에서 설정되지 않습니다.

OnStartLocalPlayer는 클라이언트의 로컬 플레이어 게임 오브젝트에 대해서만 호출되므로, 로컬 플레이어에 대한 초기화를 진행하기 적합합니다. 예를 들면, 입력 프로세싱 활성화, 플레이어 게임 오브젝트의 카메라 트래킹 활성화 등이 있습니다.

클라이언트 권한이 있는 게임 오브젝트 스폰

게임 오브젝트를 스폰한 후 해당 게임 오브젝트의 권한을 특정 클라이언트에 할당하려면 권한을 할당받는 클라이언트의 NetworkConnection을 인수로 취하는 NetworkServer.SpawnWithClientAuthority를 사용하십시오.

이 게임 오브젝트의 경우 hasAuthority 프로퍼티는 권한이 있는 클라이언트에서는 true가 되며 권한이 있는 클라이언트에서 OnStartAuthority()가 호출됩니다. 해당 클라이언트는 해당 게임 오브젝트에 대한 커맨드를 내릴 수 있게 됩니다. 다른 클라이언트와 호스트에서 hasAuthority는 false입니다.

클라이언트 권한을 가지고 스폰된 오브젝트는 반드시 NetworkIdentityLocalPlayerAuthority가 설정되어 있어야 합니다.

예를 들어, 위의 나무 스폰 예제를 수정하여 나무가 아래와 같이 클라이언트 권한을 가지도록 허용할 수 있습니다. 단, 이제는 소유 클라이언트 연결을 위해 NetworkConnection 게임 오브젝트를 전달해야 합니다.

void SpawnTrees(NetworkConnection conn) {
    int x = 0;
    for (int i = 0; i < 5; ++i)
    {
        var treeGo = Instantiate(treePrefab, new Vector3(x++, 0, 0), Quaternion.identity);
        var tree = treeGo.GetComponent<Tree>();
        tree.numLeaves = Random.Range(10,200);
        Debug.Log("Spawning leaf with leaf count " + tree.numLeaves);
        NetworkServer.SpawnWithClientAuthority(treeGo, conn);
    }
}

다음과 같이 나무 스크립트를 수정하여 서버에 커맨드를 보낼 수 있습니다.

public override void OnStartAuthority() {
    CmdMessageFromTree("Tree with " + numLeaves + " reporting in");
}

[Command]
void CmdMessageFromTree(string msg) {
    Debug.Log("Client sent a tree message: " + msg);
}

단, CmdMessageFromTree 호출을 OnStartClient에 추가할 수는 없습니다. 해당 시점에서는 권한이 아직 설정되지 않았기 때문에 호출이 실패합니다.

커스텀 플레이어 스포닝
커스텀 스폰 함수