멀티플레이어 프로젝트 설정
Network Manager HUD 사용

Network Manager 사용

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

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

  • 게임 상태 관리

  • 스폰 관리

  • 씬 관리

  • 디버깅 정보

  • 매치메이킹

  • 커스터마이징

Network Manager 시작

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

인스펙터 창에 표시된 Network Manager
인스펙터 창에 표시된 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 컴포넌트의 “스폰 정보” 섹션

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

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

플레이어 프리팹 외에도, Network Manager를 사용하여 게임플레이 중에 동적으로 스폰할 다른 프리팹도 등록해야 합니다.

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

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

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

Network Manager는 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 컴포넌트를 씬의 게임 오브젝트에 연결하고 플레이어가 시작하도록 만들 포지션에 게임 오브젝트를 배치하십시오. 원하는 만큼의 시작 포지션을 씬에 추가할 수 있습니다. Network Manager는 씬의 모든 시작 포지션을 감지하며, 각 플레이어 인스턴스가 스폰될 때 그중 하나의 포지션과 방향을 사용합니다.

Network Manager에는 시작 포지션 선택 방법을 설정하도록 해주는 Player Spawn Method 프로퍼티가 있습니다.

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

  • 라운드 로빈(Round Robin)을 선택하면 startPosition 옵션 리스트 순서대로 스폰됩니다.

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

씬 관리

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

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

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

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

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

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

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

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

커스터마이징

Network Manager에는 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;

    }

}

멀티플레이어 프로젝트 설정
Network Manager HUD 사용