Setting up a Multiplayer Project from Scratch
오브젝트 스포닝 (Object Spawning)

NetworkManager 사용하기

NetworkManager는 멀티플레이어 게임의 네트워크 상태를 관리하기 위한 컴포넌트입니다. 완전히 HLAPI에 의해 구현되어 있기 때문에 NetworkManager에 의해 수행되는 모든 작업은 다른 방법으로도 할 수 있습니다. 하지만 NetworkManager는 많은 유용한 기능을 한 곳에 정리하여 멀티플레이어 게임의 제작·실행·디버깅을 최대한 쉽게 할 수 있도록 설계되어 있습니다.

NetworkManager는 스크립트 작성을 전혀 하지 않고 사용할 수 있습니다. 에디터의 인스펙터에 의해 제어가 가능하며, 모든 기능을 설정할 수 있도록 되어 있습니다. 또한 런타임에서의 간단한 기본 사용자 인터페이스가 NetworkManagerHUD로 제공되어서, 사용자가 네트워크 게임을 제어할 수 있도록 되어 있습니다. 개발자가 NetworkManager에서 클래스를 파생시키고 제공하는 모든 가상함수 후크를 오버라이드(override)함으로써, 행동을 사용자 정의하는 식의 고급 기능도 사용할 수 있습니다.

NetworkManager의 기능에는 다음이 포함됩니다:

  • 게임 상태 관리
  • 스포닝(Spawning) 관리
  • 씬 관리
  • 디버깅 정보
  • 매치메이킹
  • 사용자 정의

NetworkManager 시작하기

NetworkManager는 멀티플레이어 게임의 핵심 제어 컴포넌트로 사용될 수 있습니다. 사용을 시작하려면, 시작 씬에 빈 게임 오브젝트를 만들거나 적당한 관리자 오브젝트를 선택하십시오. 다음으로 NetworkManager 컴포넌트를 메뉴 Network / NetworkManager에서 추가합니다. 새로 추가된 NetworkManager 컴포넌트는 대체로 다음과 같이 표시됩니다:

에디터에 있는 NetworkManager의 인스펙터로 네트워크에 관련된 다양한 것을 설정하고 제어할 수 있습니다.

NetworkManagerHUD도 NetworkManager와 함께 작동하는 컴포넌트 중 하나입니다. 게임 실행 중에 네트워크 상태를 제어할 수 있는 간단한 사용자 인터페이스를 제공합니다. 이것은 네트워크 프로젝트를 시작할 때 도움이 되지만, 완성 게임의 UI용은 아닙니다. Network ManagerHUD는 다음과 같이 표시됩니다:

실제 게임에서는 게임 상태를 제어하거나 플레이어가 플레이하고 싶은 게임의 유형을 선택할 수 있도록 하기 위한 적절한 사용자 인터페이스가 제공됩니다. 그러나 우선은 개발자가 게임을 제어하기 위해 이것을 사용합니다.

게임 상태 관리

네트워킹 멀티플레이어 게임은 3 가지 모드 내에서 실행할 수 있습니다 - 클라이언트, 전용 서버, 또는 클라이언트면서 동시에 서버이기도 한 “「호스트」” 이렇게 3가지입니다. 네트워킹은 세 가지 경우 모두에서 작동하는 코드와 에셋을 만들 수 있도록 설계되어 있습니다. 동일한 게임을 싱글 플레이어 버전과 멀티플레이어 버전으로 함께 개발할 수 있습니다.

NetworkManager는 이러한 각 모드로 들어가기 위한 여러 방법을 제공합니다. NetworkManager.StartClient(), NetworkManager.StartServer() 및 NetworkManager.StartHost()는 모든 스크립트 코드에서 사용할 수 있으며, 키보드 입력 핸들러와 커스텀 사용자 인터페이스로부터 호출할 수 있습니다. 선택적으로 표시할 수 있는 기본 런타임 제어도 이러한 함수를 호출할 수 있습니다. 또한 플레이 모드에서 사용가능한 NetworkManagerHUD 인스펙터도 이 함수를 호출할 수 있습니다:

게임 상태를 변경하기 위해 어떤 방법이 사용되더라도, networkAddress 프로퍼티와 networkPort 프로퍼티가 사용됩니다. 서버 또는 호스트가 시작되면 networkPort가 수신(listen) 포트가 됩니다. 클라이언트가 시작된 경우, networkAddress가 연결할 주소, networkPort가 연결할 포트입니다.

스포닝(Spawning) 관리

NetworkManager는 프리팹의 네트워크 오브젝트의 스포닝(spawning)을 관리하기 위해 사용할 수 있습니다. 대부분의 게임은 주요 플레이어 오브젝트로써 프리팹을 이용하고 있기 때문에, NetworkManager는 플레이어 프리팹을 드래그하는 슬롯이 있습니다. 플레이어 프리팹이 설정되면, 게임 내의 각 사용자를 위한 프리팹으로부터 플레이어 오브젝트가 자동으로 스폰됩니다. 이것은 호스트 서버 상의 로컬 플레이어와 원격 클라이언트의 원격 플레이어에 적용됩니다. 플레이어 프리팹은 반드시 NetworkIdentity 컴포넌트를 가져야 하는 점에 주의하시기 바랍니다.

플레이어 프리팹 이외에, 동적으로 스폰되어야 할 다른 오브젝트의 프리팹도 ClientScene에 등록해야 합니다. 이것은 ClientScene.RegisterPrefab() 함수로 할 수 있지만 NetworkManager에 의해 자동으로 할 수 있습니다. 스폰 리스트에 프리팹를 추가하면 그것이 자동으로 등록됩니다. NetworkManager 인스펙터에서 오브젝트 스폰 구성 섹션은 다음과 같이 표시됩니다:

플레이어 프리팹이 설정되면, 게임을 호스트로 시작할 수 있고, 플레이어 오브젝트가 스폰되는 것을 확인할 수 있습니다. 게임을 중지하면 플레이어 오브젝트가 파괴됩니다. 해당 게임의 또 다른 복사본을 실행하고, 클라이언트로서 localhost에 연결하면 또 다른 플레이어 오브젝트가 나타나고, 그 클라이언트를 중지하면 클라이언트의 플레이어 오브젝트가 파괴됩니다.

플레이어 오브젝트는 NetworkManager.OnServerAddPlayer의 기본 구현에 의해 스폰됩니다. 플레이어 오브젝트가 생성되는 방법을 사용자 정의로 하려면, 가상 함수를 재정의함으로써 가능합니다. 기본 구현은 대체로 다음과 같이 되어 있습니다:

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

NetworkServer.AddPlayerForConnection() 함수는 플레이어 오브젝트가 새로 생성된 경우에 호출해야 합니다. 그래야 그것이 스폰되고 클라이언트 커넥션과 연관되도록 만듭니다. 이것을 통해 오브젝트가 스폰될 것이기 때문에, 플레이어 오브젝트을 위해 NetworkServer.Spawn을 호출할 필요는 없습니다.

시작 위치

플레이어가 스폰(spawn)되는 위치를 제어하려면, NetworkStartPosition 컴포넌트를 사용합니다. NetworkManager는 씬 안의 NetworkStartPosition 오브젝트를 찾고, 발견한 NetworkStartPosition 오브젝트 중 어느 하나의 위치와 방향에 플레이어를 스폰합니다. 사용자 지정 코드를 사용하여 NetworkManager.startPositions 목록을 통해서 사용가능한 NetworkStartPositions에 접근할 수 있습니다. 또한 NetworkManager는 헬퍼 함수 GetStartPosition()도 제공되며, 시작 위치를 찾기 위한 OnServerAddPlayer의 구현에 사용할 수 있습니다.

To use start positions, attach a NetworkStartPosition component to an object in the play scene. There can be multiple start positions within a scene. The NetworkManager will then register the position and orientation of the object as a start position. When a client joins the game and a player is added, the player object will be created at one of the start positions, with the same position and orientation.

The NetworkManager has a property PlayerSpawnMethod which allows configuration of how startPositions are chosen.

  • Choose Random to spawn players at randomly chosen startPosition options.
  • Choose Round Robin to cycle through startPosition options in a set list.

The code for the spawn area looks like this:

if (m_PlayerSpawnMethod == PlayerSpawnMethod.Random && s_StartPositions.Count > 0)
{
    // try to spawn at a random start location
    int index = Random.Range(0, s_StartPositions.Count);
    return s_StartPositions[index];
}
if (m_PlayerSpawnMethod == PlayerSpawnMethod.RoundRobin && s_StartPositions.Count > 0)
{
    if (s_StartPositionIndex >= s_StartPositions.Count)
    {
        s_StartPositionIndex = 0;
    }
​
    Transform startPos = s_StartPositions[s_StartPositionIndex];
    s_StartPositionIndex += 1;
    return startPos;
}

씬 관리

대부분의 게임은 여러 씬을 가지고 있습니다. 일반적으로 적어도 게임을 실제로 플레이하는 씬 외에도 타이틀 화면이나 시작 메뉴 씬이 있습니다. NetworkManager는 멀티플레이어 게임에서 씬 상태와 씬 트랜지션을 자동으로 관리하도록 설계되어 있습니다. NetworkManager 인스펙터에는 두 개의 슬롯이 있습니다. 바로 offlineScene과 onlineScene입니다. 이러한 슬롯에 씬 오브젝트를 드래그하면 네트워크에서의 씬 관리가 활성화됩니다.

서버 또는 호스트가 시작되면 온라인 씬이 로드됩니다. 이것이 “현재의” 네트워크 씬입니다. 이 서버에 연결된 모든 클라이언트들도 씬 로드를 명령받습니다. 이 씬의 이름은 networkSceneName 프로퍼티에 저장됩니다.

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

또한 NetworkManager.ServerChangeScene()을 호출하면 게임이 활성화되어 있는 상태에서 씬을 변경할 수 있습니다. 이렇게 하면 그 시점에서 연결되어 있는 모든 클라이언트 씬도 변경되고 networkSceneName이 업데이트됩니다. 따라서 새로운 클라이언트도 새로운 씬을 로드할 것입니다.

네트워크 씬 관리가 활성화되어 있을 때는, NetworkManager.StartHost() 또는 NetworkManager.StopClient()를 비롯한 게임 상태 관리 함수의 어떤 호출이라도 씬 변화를 만들어낼 수 있습니다. 이것은 런타임 제어 UI에도 적용됩니다. 따라서 씬을 설정하고 이러한 함수들을 호출함으로써, 멀티플레이어 게임의 흐름을 쉽게 제어할 수 있습니다.

씬 전환은 해당 씬 안의 모든 오브젝트가 제거(destroy)되게 만듭니다. 여기는 NetworkManager도 포함됩니다! NetworkManager를 유지하고 싶다면, “Dont Destroy On Load” 체크박스를 true로 설정하는 것을 잊지마시기 바랍니다. 일반적인 단순한 경우에는 이것이 최상의 선택일 것입니다. 그러나 점진적인 프리팹 로딩 또는 다른 씬으로의 전환을 제어하기 위해서 각 씬마다 다른 설정이 필요하다면 각 씬마다 NetworkManger를 두는 것도 가능합니다.

디버깅 정보

NetworkManagerHUD 인스펙터 패널은 런타임에서 네트워크 상태에 대한 추가 정보를 보여줍니다. 여기에는 다음이 포함됩니다:

  • 네트워크 연결
  • 활성 NetworkIdentity 서버 오브젝트
  • 활성 NetworkIdentity 클라이언트 오브젝트
  • 클라이언트 피어(client peers)

또한 등록된 클라이언트 메시지 핸들러들도 미리보기 창을 통해 볼 수 있습니다.

매치메이킹(Matchmaking)

NetworkManager 런타임UI와 NetworkManager 인스펙터UI는 매치메이커 서비스와의 통신을 가능하게 합니다. NetworkManager.StartMatchmaker()함수가 매치메이킹를 활성화하고 NetworkManager.matchmaker 프로퍼티에 NetworkMatch 오브젝트를 하나를 추가합니다. 이것이 활성화되면, 기본 UI에서 그것을 사용하고, NetworkManager 상의 함수들을 콜백함으로써 매치메이킹을 쉽게 할 수 있습니다.

NetworkManager는 파생 클래스가 사용할 수 있는 가상 함수가 있으며, 이를 사용하여 매치메이커의 콜백에 대한 반응에 대한 행동을 사용자 정의할 수 있습니다.

사용자 정의

NetworkManager에는 파생 클래스가 동작을 커스터마이즈하기 위해 사용할 수 있는 가상 함수들이 있습니다. 이러한 함수들을 구현할 때, 기본 구현이 제공하는 기능을 확인하도록 하십시오. 예를 들어, OnServerAddPlayer()에서는, 연결을 위한 플레이어 오브젝트를 활성화하려면NetworkServer.AddPlayer 함수를 호출해야 합니다.

Server 또는 Host에서 호출되는 함수

// called when a client connects 
public virtual void OnServerConnect(NetworkConnection conn);

// called when a client disconnects
public virtual void OnServerDisconnect(NetworkConnection conn)
{
    NetworkServer.DestroyPlayersForConnection(conn);
}

// called when a client is ready
public virtual void OnServerReady(NetworkConnection conn)
{
    NetworkServer.SetClientReady(conn);
}

// called when a new player is added for a client
public virtual void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
    var player = (GameObject)GameObject.Instantiate(playerPrefab, playerSpawnPos, Quaternion.identity);
    NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
}

// called when a player is removed for a client
public virtual void OnServerRemovePlayer(NetworkConnection conn, short playerControllerId)
{
    PlayerController player;
    if (conn.GetPlayer(playerControllerId, out player))
    {
        if (player.NetworkIdentity != null && player.NetworkIdentity.gameObject != null)
            NetworkServer.Destroy(player.NetworkIdentity.gameObject);
    }
}

// called when a network error occurs
public virtual void OnServerError(NetworkConnection conn, int errorCode);

Client에서 호출되는 함수

// called when connected to a server
public virtual void OnClientConnect(NetworkConnection conn)
{
    ClientScene.Ready(conn);
    ClientScene.AddPlayer(0);
}

// called when disconnected from a server
public virtual void OnClientDisconnect(NetworkConnection conn)
{
    StopClient();
}

// called when a network error occurs
public virtual void OnClientError(NetworkConnection conn, int errorCode);

// called when told to be not-ready by a server
public virtual void OnClientNotReady(NetworkConnection conn);

Matchmaker를 위해 호출되는 함수

// called when a match is created
public virtual void OnMatchCreate(CreateMatchResponse matchInfo)

// called when a list of matches is received
public virtual void OnMatchList(ListMatchResponse matchList)

// called when a match is joined
public void OnMatchJoined(JoinMatchResponse matchInfo)
Setting up a Multiplayer Project from Scratch
오브젝트 스포닝 (Object Spawning)