Version: 2020.2
언어: 한국어
멀티플레이어 프로젝트 설정
네트워크 관리자 HUD 사용

네트워크 관리자 사용

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

네트워크 관리자(Network Manager)는 멀티플레이어 게임의 네트워크 관련 요소를 관리하기 위한 컴포넌트입니다.

아래는 몇 가지 네트워크 관리자 기능입니다.

  • 게임 상태 관리
  • 스폰 관리
  • 씬 관리
  • 디버깅 정보
  • 매치메이킹
  • 커스텀화

Network Manager 시작하기

Network Manager는 멀티플레이어 게임의 핵심 제어 컴포넌트입니다. 시작하려면 시작 씬에 빈 게임 오브젝트를 만든 후 NetworkManager 컴포넌트를 추가하십시오. 새로 추가한 Network Manager 컴포넌트는 다음과 같은 모습입니다.

Inspector 창에 표시된 Network Manager
Inspector 창에 표시된 Network Manager

에디터의 네트워크 관리자 인스펙터를 통해 네트워킹에 관련된 여러 사항을 설정하고 제어할 수 있습니다.

참고: 각 씬에는 하나의 액티브 Network Manager만 있어야 합니다. Network Manager 컴포넌트를 네트워크로 연결된 게임 오브젝트(Network Identity 컴포넌트가 포함된 게임 오브젝트)에 배치하면 씬이 로드될 때 Unity에서 비활성화하므로 배치하지 마십시오.

멀티플레이어 게임 개발에 이미 익숙한 사용자는 Network Manager 컴포넌트가 고수준 API(HLAPI)를 사용하여 완전히 구현되어 스크립팅을 통해서도 모든 기능을 활용할 수 있습니다. 고급 사용자의 경우 Network Manager 컴포넌트의 기능을 확장해야 할 때 스크립팅을 사용하여 NetworkManager에서 고유 클래스를 파생하고 제공되는 모든 가상 함수 후크를 오버라이드하여 해당 동작을 커스터마이즈할 수 있습니다. 하지만 Network Manager 컴포넌트는 많은 유용한 기능을 한 곳에 모아두었기 때문에 멀티플레이어 게임의 제작, 실행 및 디버깅이 매우 간소화될 수 있습니다.

게임 상태 관리

네트워킹 멀티플레이어 게임은 세 가지 모드, 즉 클라이언트, 전용 서버 또는 “호스트”(동시에 클라이언트와 서버 역할 수행) 모드에서 실행할 수 있습니다.

Network Manager HUD를 사용하는 경우 플레이어가 선택하는 옵션에 따라 어느 모드에서 시작해야 하는지 Network Manager에 자동으로 알립니다. 플레이어가 게임을 시작하도록 허용하는 자체 UI를 작성하는 경우에는 자체 코드에서 다음과 같은 메서드를 호출해야 합니다.

Network Manager 컴포넌트의 네트워크 주소 및 포트 설정
Network Manager 컴포넌트의 네트워크 주소 및 포트 설정

게임이 어느 모드(클라이언트, 서버, 호스트)에서 시작하든 관계없이 Network AddressNetwork Port 프로퍼티가 사용됩니다. 클라이언트 모드에서 게임은 지정된 주소와 포트에 연결을 시도합니다. 서버 또는 호스트 모드에서 게임은 지정된 포트에서 들어오는 연결을 수신합니다.

게임을 개발하는 동안 이러한 프로퍼티에 고정 주소 및 포트를 설정해 두면 유용합니다. 하지만 결국에는 플레이어가 연결할 호스트를 선택할 수 있어야 합니다. 이 단계에 도달하면 Network Discovery 컴포넌트(로컬 디스커버리 참조)를 사용하여 로컬 영역 네트워크(LAN)에서 주소 및 포트를 브로드캐스트하고 찾을 수 있습니다. 또한 매치메이커 서비스를 통해 플레이어가 연결할 인터넷 매치를 찾을 수 있습니다(멀티플레이어 서비스 참조).

스폰 관리

Network Manager는 프리팹에서 네트워크로 연결된 게임 오브젝트의 스포닝(네트워크 인스턴스화)을 관리합니다.

Network Manager 컴포넌트의 스폰 정보 섹션
Network Manager 컴포넌트의 “스폰 정보” 섹션

대부분의 게임에는 플레이어를 나타내는 프리팹이 있으므로 네트워크 관리자에 Player Prefab 슬롯이 있습니다. 이 슬롯에 플레이어 프리팹을 할당해야 합니다. 플레이어 프리팹 설정을 마치면 게임 내 각 사용자에 대한 플레이어 게임 오브젝트가 해당 프리팹에서 자동으로 스폰됩니다. 이는 호스팅된 서버의 로컬 플레이어와 원격 클라이언트의 원격 플레이어에게도 적용됩니다. Network Identity 컴포넌트를 플레이어 프리팹에 반드시 연결해야 합니다.

플레이어 프리팹을 할당한 후 호스트로 게임을 시작하면 플레이어 게임 오브젝트가 스폰되는 것을 볼 수 있습니다. 게임을 중지하면 플레이어 게임 오브젝트가 삭제됩니다. 게임의 다른 복사본을 빌드하고 실행한 후 localhost에 클라이언트로 연결하는 경우 네트워크 관리자가 다른 플레이어 게임 오브젝트를 표시하도록 만듭니다. 해당 클라이언트를 중지하면 해당 플레이어의 게임 오브젝트가 삭제됩니다.

플레이어 프리팹 외에도 네트워크 관리자를 사용하여 게임플레이 동안 동적으로 스폰할 다른 프리팹도 등록해야 합니다.

인스펙터에서 Registered Spawnable Prefabs라는 레이블이 지정된 리스트에 프리팹을 추가할 수 있습니다. 또한 ClientScene.RegisterPrefab() 메서드를 이용하면 코드를 통해 프리팹을 등록할 수도 있습니다.

네트워크 관리자가 하나만 있는 경우 씬에서 스폰될 수 있는 모든 프리팹을 등록해야 합니다. 씬마다 별도의 네트워크 관리자를 둔 경우에는 해당 씬과 관련된 프리팹만 등록하면 됩니다.

플레이어 인스턴스화 커스터마이징

네트워크 관리자는 NetworkManager.OnServerAddPlayer()의 구현을 통해 플레이어 게임 오브젝트를 스폰합니다. 플레이어 게임 오브젝트가 생성되는 방법을 커스터마이즈하고 싶은 경우, 해당 가상 함수를 오버라이드하면 됩니다. 아래는 기본적인 구현을 보여주는 예제 코드입니다.

public virtual void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
    var player = (GameObject)GameObject.Instantiate(playerPrefab, playerSpawnPos, Quaternion.identity);
    NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
}

참고: 커스텀 버전의 OnServerAddPlayer를 구현하는 경우 NetworkServer.AddPlayerForConnection() 메서드가 새로 생성된 플레이어 게임 오브젝트에 대해 호출되어야, 게임 오브젝트가 스폰된 후 클라이언트 연결과 연관됩니다. AddPlayerForConnection은 게임 오브젝트를 스폰하므로 NetworkServer.Spawn()을 사용할 필요가 없습니다.

시작 포지션

플레이어가 스폰되는 포지션을 제어할 때 Network Start Position 컴포넌트를 사용할 수 있습니다. 이 기능을 사용하려면 Network Start Position 컴포넌트를 씬의 게임 오브젝트에 연결하고 플레이어가 시작하도록 만들 포지션에 게임 오브젝트를 배치하십시오. 원하는 만큼 많은 시작 포지션을 씬에 추가할 수 있습니다. 네트워크 관리자는 씬의 모든 시작 포지션을 감지하며, 각 플레이어 인스턴스가 스폰될 때 그중 하나의 포지션과 방향을 사용합니다.

네트워크 관리자에는 시작 포지션 선택 방법을 설정하도록 해주는 Player Spawn Method 프로퍼티가 있습니다.

  • Random을 선택하면 플레이어는 startPosition 옵션 중 임의로 선택된 곳에 스폰됩니다.

  • Round Robin을 선택하면 startPosition 옵션 리스트 순서대로 스폰됩니다.

Random 또는 Round Robin 모드가 게임에 적절하지 않다면 코드를 사용하여 시작 포지션 선택 방법을 커스터마이즈할 수 있습니다. 이용 가능한 Network Start Position 컴포넌트는 NetworkManager.startPositions 리스트에서 확인할 수 있습니다. 또한 Network Manager의 GetStartPosition() 헬퍼 메서드를 사용하면 시작 포지션을 찾도록 OnServerAddPlayer를 구현할 수 있습니다.

씬 관리

대부분의 게임에는 둘 이상의 씬이 있습니다. 최소한 타이틀 화면이나 시작 메뉴 씬이 있을 것이며, 실제로 게임이 플레이되는 씬도 있어야 하기 때문입니다. Network Manager를 이용하면 멀티플레이어 게임에 맞는 방식으로 씬 상태와 전환을 자동으로 관리할 수 있습니다.

네트워크 관리자 인스펙터에는 두 개의 슬롯, 즉 Offline Scene과 Online Scene이 있습니다. 이 슬롯에 씬 에셋을 드래그하면 네트워크 씬 관리가 활성화됩니다.

서버나 호스트가 시작되면 Online Scene이 로드됩니다. 이 씬이 현재 네트워크 씬이 됩니다. 해당 서버에 연결하는 모든 클라이언트 역시 해당 씬을 로드하도록 지시를 받습니다. 이 씬의 이름은 networkSceneName 프로퍼티에 저장됩니다.

서버나 호스트가 정지되거나 클라이언트가 연결 해제되어 네트워크가 중단되면 오프라인 씬이 로드됩니다. 이를 통해 멀티플레이어 게임에서 연결이 해제되면 자동으로 메뉴 씬으로 돌아가도록 할 수 있습니다.

또한 NetworkManager.ServerChangeScene()을 호출하여 게임이 활성화된 경우에 씬을 변경할 수 있습니다. 이는 현재 연결된 클라이언트 역시 씬을 변경하게 되며, networkSceneName을 업데이트하여 새로운 클라이언트 역시 새로운 씬을 로드하도록 할 수 있습니다.

네트워크 씬 관리가 활성화된 경우 NetworkManager.StartHost()NetworkManager.StopClient()와 같은 게임 상태 관리 함수 호출은 씬 변경을 발생시킵니다. 런타임 제어 UI에도 적용됩니다. 씬을 설정하고 이 메서드를 호출하여 멀티플레이어 게임의 플로우를 제어할 수 있습니다.

씬을 변경하면 이전 씬의 모든 게임 오브젝트가 삭제되니 유의하시기 바랍니다.

네트워크 관리자는 씬이 바뀌더라도 유지되어야 합니다. 그러지 않으면 씬이 바뀔 때 네트워크 연결이 끊어집니다. 이를 방지하려면 인스펙터에서 Don’t Destroy On Load 박스를 선택하십시오. 각 씬에 다른 설정을 가진 별도의 네트워크 관리자를 둘 수도 있으며, 이는 인크리먼트 프리팹 로딩이나 다른 씬 전환 방법을 사용하고자 할 때 유용합니다.

커스텀화

네트워크 관리자에는 NetworkManager에서 상속받는 공유 파생 클래스를 생성하여 커스터마이즈할 수 있는 가상 함수가 있습니다. 이 함수를 구현하는 경우 기본 구현 방식이 제공하는 기능을 잘 확인해야 합니다. 예를 들어, OnServerAddPlayer()의 경우 NetworkServer.AddPlayer 함수가 호출되어야만 플레이어 게임 오브젝트가 연결할 수 있도록 활성화됩니다.

호스트/서버 및 클라이언트에서 발생할 수 있는 모든 콜백입니다. 때에 따라서는 기본 클래스 함수를 호출하여 기본 동작을 유지하는 것이 중요합니다. 구현되는 모습은 Bitbucket 저장소 네트워킹 문서에서 확인할 수 있습니다.

using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.Match;

public class CustomManager : NetworkManager {
    // Server callbacks
    public override void OnServerConnect(NetworkConnection conn) {
        Debug.Log("A client connected to the server: " + conn);
    }

    public override void OnServerDisconnect(NetworkConnection conn) {
        NetworkServer.DestroyPlayersForConnection(conn);
        if (conn.lastError != NetworkError.Ok) {
            if (LogFilter.logError) { Debug.LogError("ServerDisconnected due to error: " + conn.lastError); }
        }
        Debug.Log("A client disconnected from the server: " + conn);
    }
    public override void OnServerReady(NetworkConnection conn) {
        NetworkServer.SetClientReady(conn);
        Debug.Log("Client is set to the ready state (ready to receive state updates): " + conn);
    }
    public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId) {
        var player = (GameObject)GameObject.Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
        NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
        Debug.Log("Client has requested to get his player added to the game");
    }

    public override void OnServerRemovePlayer(NetworkConnection conn, PlayerController player) {
        if (player.gameObject != null)
            NetworkServer.Destroy(player.gameObject);
    }

    public override void OnServerError(NetworkConnection conn, int errorCode) {
        Debug.Log("Server network error occurred: " + (NetworkError)errorCode);
    }

    public override void OnStartHost() {
        Debug.Log("Host has started");
    }

    public override void OnStartServer() {
        Debug.Log("Server has started");
    }

    public override void OnStopServer() {
        Debug.Log("Server has stopped");
    }

    public override void OnStopHost() {
        Debug.Log("Host has stopped");
    }

    // Client callbacks
    public override void OnClientConnect(NetworkConnection conn)
    {
        base.OnClientConnect(conn);
        Debug.Log("Connected successfully to server, now to set up other stuff for the client...");
    }

    public override void OnClientDisconnect(NetworkConnection conn) {
        StopClient();
        if (conn.lastError != NetworkError.Ok)
        {
            if (LogFilter.logError) { Debug.LogError("ClientDisconnected due to error: " + conn.lastError); }
        }
        Debug.Log("Client disconnected from server: " + conn);
    }

    public override void OnClientError(NetworkConnection conn, int errorCode) {
        Debug.Log("Client network error occurred: " + (NetworkError)errorCode);
    }

    public override void OnClientNotReady(NetworkConnection conn) {
        Debug.Log("Server has set client to be not-ready (stop getting state updates)");
    }

    public override void OnStartClient(NetworkClient client) {
        Debug.Log("Client has started");
    }

    public override void OnStopClient() {
        Debug.Log("Client has stopped");
    }

    public override void OnClientSceneChanged(NetworkConnection conn) {
        base.OnClientSceneChanged(conn);
        Debug.Log("Server triggered scene change and we've done the same, do any extra work here for the client...");
    }
}

NetworkManager용 인스펙터는 일부 연결 파라미터 및 타임아웃을 변경할 수 있는 기능을 제공합니다. 일부 파라미터는 여기에 표시되지 않았지만, 코드를 통해 변경할 수 있습니다.

using UnityEngine;
using UnityEngine.Networking;

public class CustomManager : NetworkManager {
    // Set custom connection parameters early, so they are not too late to be enforced
    void Start()
    {
        customConfig = true;
        connectionConfig.MaxCombinedReliableMessageCount = 40;
        connectionConfig.MaxCombinedReliableMessageSize = 800;
        connectionConfig.MaxSentMessageQueueSize = 2048;
        connectionConfig.IsAcksLong = true;
        globalConfig.ThreadAwakeTimeout = 1;
    }
}
멀티플레이어 프로젝트 설정
네트워크 관리자 HUD 사용