반응형

UNet 

UNet 지원 중단과 새로운 네트워크 지원 예정


이전 포스트에서 유니티에서 지원하는 네트워크 게임 구현 API인 UNet에 관한 내용을 다룬 적이 있으며, 추후에 추가적인 내용을 다룰 예정이었지만 지난 2018년 8월 2일에 유니티 공식 블로그에 게시된 글에 의하면 UNet은 deprecated 되었으며 새로운 네트워크 API를 지원하기 위한 준비를 하고 있다고 이야기하고 있다.


 

UNet 튜토리얼 (1) - 개요 글에서 이야기하였듯이 UNet의 구조는 위의 이미지처럼 전송 계층에 가까운 LLAPI(Low Level API)와 게임에 필요한 기능을 제공하는 HLAPI(High Level API)로 나누어져 있다. 이 LLAPI와 HLAPI는 유니티의 계획에 의하면 :


1. HLAPI는 2018.4(LTS) 이후 더 이상 유니티와 함께 제공되지 않음. 2018.4(LTS) 출시일 이후 2년 동안 유니티의 장기 지원 정책에 따라 중요한 수정 사항 제공.

2. LLAPI는 2019.4(LTS) 이후 더 이상 유니티와 함께 제공되지 않음. 2019.4(LTS) 출시일  이후 2년 동안 중요한 수정 사항 제공.

3. 유니티 릴레이 서버 및 레거시 매치 메이커 서비스는 2018.4(LTS) 제공 이후 3년 이상 계속 운영하며 다음 서비스로 전환 계획.


UNet은 더 이상 사용되지 않지만 ECS(Entity Component System)와 대응이 되는 차세대 네트워크 기능을 곧 제공할 예정이다. 유니티가 제공할 예정인 기능은 다음과 같다.


1. 기존 UNet의 HLAPI 및 LLAPI를 대체하며 DOTS와 호환되는 새로운 네트워킹 계층.

2. P2P 지원 릴레이 서버를 대체하는 멀티 플레이 게임 서버 호스팅 서비스.

3. 레거시 매치 메이커 서비스를 대체하는 새로운 매치 메이킹 서비스.


유니티의 네트워크 API 제공 계획은 다음과 같습니다.




반응형
반응형

QoS 채널(Quality of Service)


유니티 네트워크에서는 메시지의 전송 품질을 QoS(Quality of Service)라고 하는데, 네트워크 매니저에서 채널을 추가하고, 그 채널의 전송 품질을 설정할 수 있다. 그리고 NetworkBehaviour를 상속받는 클래스를 정의할 때, NetworkSetting 어트리뷰트를 통해서 이 클래스의 객체가 네트워크 메시지를 보낼 때, 어떤 채널을 통해서 메시지를 보낼지, 그 채널이 어떤 수준의 전송 품질로 메시지를 전송할 것인지를 설정할 수 있다.





전송 품질 타입(QosType)


우선은 유니티 네트워크에서는 어떤 종류의 전송 품질의 정의하고 지원하는지 알아야 하는데, 이러한 전송 품질에 대한 타입을 QosType이라는 열거형으로 정의하고 있다.


유니티 네트워크에서 정의하는 전송 품질의 종류는 다음과 같다.


  • Unreliable - 전송되는 메시지의 도착이나 순서를 보장하지 않는다.
  • UnreliableFragmented - 전송되는 메시지의 도착이나 순서를 보장하지 않지만 메시지를 최대 32개로 분할된 메시지를 허용한다.
  • UnreliableSequenced - 메시지의 도착은 보장하지 않지만 순서는 보장한다. 만약 지금 도착한 메시지보다 이전에 전송된 메시지는 무시한다.
  • Reliable - 메시지의 도착은 보장하지만 순서는 보장하지 않는다.
  • ReliableFragmented - 메시지의 도착을 보장하며, 메시지 당 최대 32개로 분할된 메시지를 허용한다.
  • ReliableSequenced - 메시지의 도착과 순서를 보장한다.
  • StateUpdate - 기본적으로 메시지의 도착이나 순서를 보장하지 않고, 전송 버퍼에 쌓인 메시지 중에 가장 최근의 마지막 메시지만 전송한다.
  • ReliableStateUpdate - 메시지의 도착을 보장하며, 전송 버퍼에 쌓인 메시지 중에 가장 최근의 마지막 메시지만 전송한다.
  • AllCostDelivery - 상대가 수신을 받았다는 확인을 받을 때까지 높은 빈도로 재전송하는 가장 신뢰성이 높은 메시지.
  • UnreliableFragmentedSequenced - 전송되는 메시지의 도착은 보장하지 않지만, 순서를 보장하며, 최대 32개로 분할된 메시지를 허용한다.
  • ReliableFragmentedSequenced - 전송되는 메시지의 도착과 순서를 보장하며, 최대 32개로 분할된 메시지를 허용한다.


제일 기본적인 전송 품질은 Reliable과 Unreliable인데, 기본 네트워크로 따지면 Reliable은 TCP, Unreliable은 UDP와 같다. 그리고 여기에 추가적으로 순서를 보장할 것인지, 메시지의 분할을 허용할 것인지, 버퍼에 쌓인 메시지 중에 가장 마지막 메시지만 보낼 것인지에 따라서 전송 품질의 종류가 나누어진다.





채널 추가하기(Add Channel)


원하는 채널을 사용하기 위해서는 우선 네트워크 매니저에 채널을 추가 해주어야 한다.


Inspector 뷰에서 채널 추가하기




Inspector 뷰에서 추가하는 방법은 매우 간단하다. Inspector 뷰에서 네트워크 매니저의 내용들을 살펴보면 Advenced Configuration에 체크를 해주면 Qos Channels라는 것이 생기는데, 여기서 + 버튼을 눌러서 채널을 추가하고 드롭다운 메뉴에서 QosType을 선택하면 된다.


주의사항


네트워크 매니저에 채널을 추가할 때, 주의해야할 사항이 있다. 특히 서버와 클라이언트를 나누어서 빌드하는 경우에는, 서버와 클라이언트 간에 채널이 일치하지 않는 일이 발생하지 않도록 주의를 기울여야 한다.


만약에 서버와 클라이언트의 채널이 일치하지 않는다면, 클라이언트는 CRC Mismatch 오류를 발생시키며, 서버에 접속할 수 없게 된다.





채널 사용하기


채널을 사용하는 방법은 추가하는 것보다 더 간단하다.


using UnityEngine.Networking;

[NetworkSettings(channel = 0, sendInterval = 0.1f)]
public class Player : NetworkBehaviour
{
}


NetworkBehaviour를 상속받는 클래스에 NetworkSettings 어트리뷰트를 이용해서 channel 값에 그 네트워크 오브젝트가 사용하고자 하는 채널의 번호를 넣어주면 된다. 아무것도 넣지 않았을 경우에는 기본으로 0번 채널을 사용하고, 채널을 추가하지 않았다면 Reliable Sequenced 통신 채널을 기본으로 사용한다.





Qos 채널을 사용한 네트워크 사용량 최적화


각기 다른 전송 품질을 가진 전송 채널을 여러 개를 두고 각 메시지 타입의 특성에 알맞은 전송 품질을 가진 채널로 메시지를 전송하는 것만으로도 네트워크 전송량 최적화에 상당히 많은 도움이 된다.


네트워크 메시지 중에서 Rpc나 Command 같은 원격 액션은 무관하지만 SyncVar로 동기화 되는 멤버 변수의 경우, StateUpdate나 ReliableStateUpdate 채널을 사용하지 않는다면, 값이 업데이트되는 속도가 Send Interval보다 짧을 때, 변경된 메시지를 전송 버퍼에 쌓아두었다가 Send Interval이 끝나서 메시지를 전송하는 순간에 버퍼에 쌓여있던 메시지를 한꺼번에 전송해버린다. 즉, StateUpdate가 아닌 Reliable이나 Unreliabe 채널을 통해서 전송되는 SyncVar 메시지는 전송량이 StateUpdate 채널을 통해 전달되는 메시지보다 많을 수 밖에 없다.


그렇기 때문에 SyncVar를 사용할 때는 이 변수를 어떤 전송 품질로 전송하는게 적절한 지 충분히 고민한 후에 채널을 정해서 전송하는 것이 좋다.



참고

유니티 스크립트 API 레퍼런스 - QoS Type (https://docs.unity3d.com/ScriptReference/Networking.QosType.html)

반응형
  1. 2018.10.05 15:32

    감사합니다. 많은 도움을 받고 있습니다. 유니티 네트워크는 한글 자료가 많이 부족한데 덕분에 배워갑니다^^

반응형

클라이언트의 준비(Client Ready)


지난 섹션에서는 전반적인 네트워크 매니저 콜백에 대해서 알아보았다. 이번 섹션에서는 네트워크 과정 중에 하나인 클라이언트의 준비에 대해서 알아보자. 클라이언트의 준비란 서버에 접속한 클라이언트가 준비가 되었음을 알리는 과정인데, 유넷에서는 클라이언트 측에서 ClientScene.Ready() 함수를 호출함으로써 클라이언트가 서버에 동기화될 준비가 끝났음을 알린다.


이러한 클라이언트의 준비라는 과정을 네트워크 매니저 콜백 섹션에서 이야기하지 않고 별도의 섹션을 따로 만들어 이야기하는 것은 유넷에서의 클라이언트 준비라는 것이 상당히 중요한 역할을 하기 때문이다.





클라이언트 준비의 역할과 의미


지난 섹션 중 UNet Tutorial (7) - 오브젝트 스폰(Object Spawn) 의 내용 중에 다음과 같은 내용이 있다.


"스폰된 오브젝트는 유니티 네트워크의 스포닝 시스템이 관리하게 되며, 스포닝 시스템에 속하게 된 오브젝트가 서버에서 변화가 있으면 그것이 클라이언트에도 전송되고, 서버에서 오브젝트가 소멸하면 클라이언트에서도 소멸하게 된다. 그리고 스폰된 오브젝트는 서버가 관리하는 네트워크 오브젝트 집합에도 추가되기 때문에, 이후에 다른 클라이언트가 게임에 참여하더라도 프로그래머가 별도의 처리를 만들 필요없이 자동으로 오브젝트가 소환되고 동기화 되어야할 값들이 동기화된다."

스폰된 오브젝트는 유니티 네트워크의 스포닝 시스템이 관리하게 되며, 스포닝 시스템에 속하게 된 오브젝트가 서버에서 변화가 있으면 그것이 클라이언트에도 전송되고, 서버에서 오브젝트가 소멸하면 클라이언트에서도 소멸하게 된다. 그리고 스폰된 오브젝트는 서버가 관리하는 네트워크 오브젝트 집합에도 추가되기 때문에, 이후에 다른 클라이언트가 게임에 참여하더라도 프로그래머가 별도의 처리를 만들 필요없이 자동으로 오브젝트가 소환되고 동기화 되어야할 값들이 동기화된다.

출처: http://wergia.tistory.com/106?category=768883 [베르의 프로그래밍 노트]
스폰된 오브젝트는 유니티 네트워크의 스포닝 시스템이 관리하게 되며, 스포닝 시스템에 속하게 된 오브젝트가 서버에서 변화가 있으면 그것이 클라이언트에도 전송되고, 서버에서 오브젝트가 소멸하면 클라이언트에서도 소멸하게 된다. 그리고 스폰된 오브젝트는 서버가 관리하는 네트워크 오브젝트 집합에도 추가되기 때문에, 이후에 다른 클라이언트가 게임에 참여하더라도 프로그래머가 별도의 처리를 만들 필요없이 자동으로 오브젝트가 소환되고 동기화 되어야할 값들이 동기화된다.

출처: http://wergia.tistory.com/106?category=768883 [베르의 프로그래밍 노트]


위의 내용과 같이 네트워크를 통해서 스폰된 오브젝트는 유니티 네트워크의 스포닝 시스템이 관리하며, 게임 중에 다른 클라이언트가 참가하거나 게임 중에 접속이 끊어졌다가 재접속하는 유저에게 별도의 처리 없이 오브젝트나 값이 동기화 되는데, 바로 이 동기화 시작의 기준이 클라이언트의 준비 상태다.


즉, 클라이언트가 준비를 끝마친 후에야 네트워크 오브젝트들의 동기화가 시작된다. 거기에 지난 섹션들에서 언급한 SyncVar와 Hook, Command와 ClientRpc 역시 클라이언트 준비 이후에만 동기화 되고 원격 액션을 주고 받을 수 있게 된다. 이 말인 즉슨, SyncVar나 Command, ClientRpc로 게임을 준비하기 위한 초기화를 진행하려고 해서는 안된다는 것이다. 게임이 시작되기 전에 필요한 초기화를 SyncVar, Command, ClientRpc로 할 경우, 처음 접속은 올바르게 될 수도 있지만 재접속이나 게임 진행중에 접속하는 경우에 심각한 문제를 초래할 수 있다.


게임의 초기화를 위한 작업은 클라이언트의 네트워크가 준비되기 이전에 하는 것이 옳다. 원격 액션을 보내는 Command나 ClientRpc는 클라이언트가 준비된 이후에나 가능한데 그렇다면 클라이언트가 준비되기 이전에는 어떻게 통신해야 하는가는 이 다음 섹션에서 설명할 네트워크 메시지를 사용하면 된다. 네트워크 메시지의 경우에는 클라이언트가 준비되었느냐를 따지지 않고 서버와 클라이언트가 연결만 되어 있으면 주고 받을 수 있기 때문이다.





원하는 시점에서 클라이언트 준비하기


지난 네트워크 매니저 콜백 섹션에서 가볍게 이야기 했듯이, 유넷의 기본 네트워크 매니저에서는 클라이언트가 서버에 접속하면 별다른 처리 없이도 자동으로 ClientScene.Ready() 함수가 호출되어 클라이언트가 동기화될 준비가 끝났음을 서버에 알린다.


만약에 클라이언트가 서버에 접속하자마자 준비하는 것을 원하지 않고 일련의 다른 과정을 거친 후에 준비하기를 원한다면 다음과 별도의 처리가 필요하다.


public override void OnClientConnect(NetworkConnection conn)
{
    base.OnClientConnect(conn);
}


OnClientConnect 콜백을 커스텀 네트워크 매니저에서 오버라이드하면 위와 같이 코드가 작성되는데, 저기서 부모 클래스의 OnClientConnect를 호출하는 것을 볼 수 있다. 바로 이 부모 클래스의 OnClientConnect에서 ClientScene.Ready()와 ClientScene.AddPlayer()가 호출되기 때문에 클라이언트가 서버에 접속하자마자 자동으로 준비되는 것이다.


public override void OnClientConnect(NetworkConnection conn)
{
    //base.OnClientConnect(conn);
}


그렇기 때문에 서버에 접속하자 마자 클라이언트가 준비 신호를 보내고, 플레이어 객체를 생성하기를 원하지 않는다면 위의 코드 예시처럼 부모 클래스의 OnClientConnect()를 호출하는 라인을 주석처리하거나 삭제하고 아래의 예시 코드와 같이 개발자가 원하는 별도의 처리를 한 후에 ClientScene.Ready()와 ClientScene.AddPlayer()를 호출해주면 된다.


public override void OnClientConnect(NetworkConnection conn)
{
    //base.OnClientConnect(conn);

    /*
     * 개발자가 원하는 별도의 처리
     */

    ClientScene.Ready(client.connection);
    ClientScene.AddPlayer(0);
}



반응형
반응형

네트워크 매니저 콜백(Network Manager Callback)


지난 섹션에서는 유넷에서 제공하는 Network Manager HUD의 기능을 직접 구현해보았다. 그리고 그것을 테스트하는 중간에 네트워크 매니저가 보내는 콜백을 통해서 서버와 호스트, 클라이언트의 실행 여부와 접속 여부를 확인할 수 있었다. 네트워크 매니저 콜백은 그 외에도 유니티 네트워크가 지정한 특정한 상황을 알리고 그것에 대한 기본적인 처리를 하는 역할을 한다.


그 상황에 대한 기본적인 처리 외의 필요한 것은 콜백을 상속받아서 처리에 대한 코드를 직접 작성해주면 된다.


우선은 네트워크 매니저 콜백의 종류에 대해서 알아보자. 네트워크 매니저 콜백은 서버에서 호출되는 콜백, 클라이언트에서 호출되는 콜백, 호스트에서 호출되는 콜백, 이렇게 세 가지로 나눌 수 있다. 단, 호스트의 경우에는 서버와 클라이언트의 역할을 겸하기 때문에 호스트에서 호출되는 콜백 이외에도 서버 콜백과 클라이언트 콜백이 함께 호출된다.





서버 콜백(Server Callback)


public override void OnStartServer()


서버가 시작 되었을 때 호출되는 콜백이다.


public override void OnStopServer()


서버가 정지 되었을 때 호출되는 콜백이다.


public override void OnServerConnect(NetworkConnection conn)


서버에 새로운 클라이언트가 연결 되었을 때 호출되는 콜백이다. 매개변수로 새롭게 접속한 클라이언트의 Network Connection이 제공된다.


public override void OnServerReady(NetworkConnection conn)


서버에 접속한 클라이언트가 준비 되었을 때 호출되는 콜백이다. 매개변수로 준비된 클라이언트의 Network Connection이 제공된다.


public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)

public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader)


서버에 접속한 클라이언트가 ClientScene.AddPlayer() 함수를 호출해서 새 플레이어를 추가할 때, 호출되는 콜백이다. 이 콜백의 기본 처리는 네트워크 매니저에 등록된 playerPrefab을 이용해서 새 플레이어 객체를 소환한다. 매개변수를 통해서 플레이어를 생성한 클라이언트의 Network Connection과 새로 생성되는 플레이어 컨트롤러의 id, 클라이언트에서 보내는 추가적인 메시지가 전달된다.


public override void OnServerRemovePlayer(NetworkConnection conn, PlayerController player)


클라이언트가 플레이어를 제거할 때, 서버에서 호출되는 콜백이다. 이 콜백의 기본 처리는 해당 플레이어 객체를 파괴한다. 매개변수를 통해서 플레이어를 제거한 클라이언트의 Network Connection과 제거되는 플레이어 객체가 전달된다.


public override void OnServerDisconnect(NetworkConnection conn)


서버에 접속해있던 클라이언트가 연결이 끊어졌을 때 호출되는 콜백이다. 매개변수를 통해서 연결이 끊어진 클라이언트의 Network Connection이 전달된다.


public override void OnServerError(NetworkConnection conn, int errorCode)


클라이언트 연결에 대한 네트워크 오류가 발생하면 서버에서 호출되는 콜백이다. 매개변수를 통해서 문제가 발생한 클라이언트의 Network Connection과 에러 코드가 전달된다.


public override void OnServerSceneChanged(string sceneName)


씬 로드가 완료되거나, 서버에서 ServerChangeScene()로 씬 로드가 시작될 때 호출되는 콜백이다. 매개변수를 통해서 새롭게 로드되는 씬의 이름이 전달된다.





클라이언트 콜백(Client Callback)


public override void OnStartClient(NetworkClient client)


클라이언트가 시작할 때 호출되는 콜백이다. 매개변수를 통해서 네트워크 시스템에서 사용되는 네트워크 클라이언트 객체가 전달된다. 이 네트워크 클라이언트에는 네트워크 서버에 연결하는데 사용되는 Network Connection이 포함되어 있다. 이 콜백의 기본 처리는 매개변수로 전달받은 네트워크 클라이언트 객체를 NetworkManager.client 멤버 변수에 할당하는 역할을 주로한다. 이 client 멤버 변수를 이용해서 서버에 메시지를 보내거나 서버의 상태를 확인할 수 있다.


public override void OnStopClient()


클라이언트가 정지 되었을 때 호출되는 콜백이다.


public override void OnClientConnect(NetworkConnection conn)


클라이언트가 서버에 연결되었을 때 호출되는 콜백이다. 매개변수를 통해서 서버의 Network Connection이 전달된다. 그리고 이 콜백은 자기 자신의 클라이언트가 서버에 연결 되었을 때만 호출된다. 이 콜백의 기본 처리는 클라이언트를 준비(Ready) 시키고 플레이어를 추가(Adds a player)하는 작업을 한다. 만약 클라이언트가 서버에 연결되자마자 준비하고 플레이어를 추가하는 것을 원하지 않고 별도의 작업을 한 이후에 준비 하고 플레이어를 추가하길 원한다면 base.OnClientConnect()의 호출을 주석처리하거나 코드를 제거하는게 좋다.


public override void OnClientDisconnect(NetworkConnection conn)


서버와의 연결이 해제 되었을 때 호출되는 콜백이다. 매개변수를 통해서 서버의 Network Connection이 전달된다.


public override void OnClientError(NetworkConnection conn, int errorCode)


네트워크 오류가 발생했을 때 호출되는 콜백이다. 매개변수를 통해서 서버의 Networ kConnection과 에러 코드가 전달된다.


public override void OnClientNotReady(NetworkConnection conn)


서버에서 더 이상 클라이언트가 준비 상태가 아님을 알려왔을 때 호출되는 콜백이다. 매개변수를 통해서 서버의 Network Connection이 전달된다.


이 콜백은 주로 씬 전환에 사용된다.





호스트 콜백(Host Callback)


public override void OnStartHost()


호스트가 시작 되었을 때 호출되는 콜백이다.


public override void OnStopHost()


호스트가 정지 되었을 때 호출되는 콜백이다.


호스트 콜백의 경우에는 종류가 시작과 정지 두 가지 뿐이다. 호스트는 서버와 클라이언트를 겸하기 때문에 호스트 콜백과 더불어 서버 콜백과 클라이언트 콜백을 함께 호출한다.


네트워크 매니저에는 위에서 설명한 콜백 이외에도 몇 가지 콜백들이 더 있으나, 나머지 콜백들은 유니티 매치메이커에서 사용되는 콜백이기 때문에 이번 세션에서는 소개하지 않고 추후에 매치메이커 세션에서 설명하도록 하겠다.





콜백의 호출 순서


서버에 새로운 클라이언트가 연결되거나 연결이 끊어졌을 때 등, 여러가지 상황을 처리하는데 콜백은 유용하게 사용된다. 이러한 상황을 처리할 때에 순서가 중요한 경우가 많기 때문에 콜백의 호출 순서를 확인 해두는 것이 좋다.


서버 콜백의 호출 순서


1단계 : 서버 시작


- StartServer() 함수 호출

- OnStartServer

- OnServerSceneChanged


2단계 : 클라이언트 접속


- OnServerConnect

- OnServerReady

- OnServerAddPlayer


3단계 : 클라이언트 연결 해제


- OnServerDisconnect


4단계 : 서버 중지


- OnStopServer





클라이언트 콜백의 호출 순서


1단계 : 클라이언트 시작


- StartClient() 함수 호출

- OnStartClient

- OnClientConnect

- OnClientSceneChanged


2단계 : 서버 중지 or 클라이언트 중지


- OnStopClient

- OnClientDisconnect





호스트 콜백의 호출 순서


1단계 : 호스트 시작


- StartHost() 함수 호출

- OnStartHost

- OnStartServer

- ServerConnect

- OnStartClient

- OnClientConnect

- OnServerSceneChanged

- OnServerReady

- OnServerAddPlayer

- OnClientSceneChanged


2단계 : 클라이언트 접속


- OnServerConnect

- OnServerReady

- OnServerAddPlayer


3단계 : 클라이언트 연결 해제


- OnServerDisconnect


4단계 : 호스트 중지


- OnStopHost

- OnStopServer

- OnStopClient


앞에서 이야기 한 것과 같이 호스트는 서버와 클라이언트의 역할을 겸하기 때문에 서버의 콜백과 클라이언트의 콜백이 함께 호출된다.

반응형
반응형

UNet Tutorial (6) - SyncVar와 Hook


지난 Unet Tutorial 섹션에서는 Command와 ClientRpc를 이용한 클라이언트와 서버 간의 액션 통신 방법에 대해서 알아보았다. 이것은 함수를 통한 동작 수행을 처리하는 것이었다. 하지만 이번 섹션에서 알아볼 SyncVar와 Hook은 NetworkBehaviour를 상속받는 클래스의 멤버 변수를 동기화하기 위한 것이다.


지난 섹션에서 배운 Command 와 ClientRpc를 이용해서도 멤버변수에 대한 동기화를 구현할 수는 있다. Command와 ClientRpc를 통한 멤버 변수 동기화를 구현한 코드는 다음과 같다.


using UnityEngine;
using UnityEngine.Networking;

public class Player : NetworkBehaviour
{
    // 플레이어의 체력
    public int hp;

    void Start()
    {
        // 서버에서 플레이어가 생성되면 hp를 100으로 만들고 Rpc를 통해 클라이언트에 알려준다.
        if (isServer)
        {
            hp = 100;
            RpcSyncHp(hp);
        }
    }
   
    [ClientRpc]
    // 클라이언트는 Rpc를 통해 현재 hp를 받으면 자신의 객체에 적용한다.
    public void RpcSyncHp(int hp)
    {
        this.hp = hp;
    }

    [Command]
    // 서버는 Cmd로 플레이어가 받아야될 데미지를 받으면 현재 hp에서 데미지를 빼고 남은 hp를 Rpc로 클라이언트에 알려준다.
    public void CmdDamaged(int dmg)
    {
        hp -= dmg;
        RpcSyncHp(hp);
    }

    void Update()
    {
        // 클라이언트에서 스페이스 버튼을 누르면 Cmd를 호출해서 서버에 플레이어가 입어야될 데미지를 알려준다.
        if (isClient && isLocalPlayer)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                CmdDamaged(10);
            }
        }
    }
}

위의 예시에서는 CmdDamaged를 호출할 때는 클라이언트에서 데미지 값을 넣도록 했지만 실제로 구현할 때는 저렇게 하지 않도록 하자. 클라이언트에서 중요한 값을 넣을 수 있도록하면 해킹에 취약해진다.


Command와 ClientRpc를 이용하여 멤버 변수 동기화 구현은 가능하지만 약간은 복잡하다.





SyncVar를 통한 멤버 변수 동기화


SyncVar를 통해 멤버 변수 동기화를 이용하면 아래의 예시 코드처럼 조금 더 간단한 코드가 작성 가능해진다.


using UnityEngine;
using UnityEngine.Networking;

public class Player : NetworkBehaviour
{
    [SyncVar]
    // 플레이어의 체력
    public int hp;

    void Start()
    {
        // 서버에서 플레이어가 생성되면 hp를 100으로 만든다.
        if (isServer)
        {
            hp = 100;
        }
    }

    [Command]
    // 서버는 Cmd로 플레이어가 받아야될 데미지를 받으면 현재 hp에서 데미지를 뺀다.
    public void CmdDamaged(int dmg)
    {
        hp -= dmg;
    }

    void Update()
    {
        // 클라이언트에서 스페이스 버튼을 누르면 Cmd를 호출해서 서버에 플레이어가 입어야될 데미지를 알려준다.
        if (isClient && isLocalPlayer)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                CmdDamaged(10);
            }
        }
    }
}


위의 예시 코드처럼 동기화하고자 하는 멤버 변수에 SyncVar 어트리뷰트를 붙여주면 서버에서 hp의 값이 변경되면 클라이언트의 해당 객체에 변경된 값을 알려주고 동기화하게 된다. 이러한 SyncVar의 동기화에서 알고있어야 할 점은 이 멤버 변수 동기화는 서버에서 클라이언트의 방향으로만 이루어진다는 점이다. 즉, 서버에서 변수가 수정되면 모든 클라이언트로 동기화되고, 클라이언트에서는 이 변수의 값을 아무리 수정해도 다른 클라이언트나 서버에서는 변경이 되지 않는다는 점이다.





Hook


멤버 변수가 변경된 것을 클라이언트에서 동기화 받고 난 뒤에 클라이언트에서 처리해야할 작업이 더 있는 경우가 더러 있다. 위의 코드를 예를 들자면 hp가 동기화된 이후에 클라이언트 측에서는 변경된 hp의 양에 맞춰서 hp ui를 변경해 주어야할 것이다. 그런 것을 처리하는 역할을 할 수 있는 것이 바로 hook이다. hook의 구현은 다음 예시 코드와 같이할 수 있다.


using UnityEngine;
using UnityEngine.Networking;

public class Player : NetworkBehaviour
{

    public delegate void OnChangeHp(int hp);

    public OnChangeHp onChangeHp;


    [SyncVar(hook = "ChangeHookHp")]
    // 플레이어의 체력
    public int hp;
    void ChangeHookHp(int hp)
    {

        // hook을 이용하는 경우에는 동기화해야할 값이 매개 변수로 넘어오고 그것을 직접 대입해주어야 한다.
        this.hp = hp;

        // 만약 delegate event에 hp ui의 함수를 등록해두었다면 hp ui가 갱신될 것이다.

        onChangeHp(this.hp);
    }

    void Start()
    {
        // 서버에서 플레이어가 생성되면 hp를 100으로 만들고 Rpc를 통해 클라이언트에 알려준다.
        if (isServer)
        {
            hp = 100;
        }
    }

    [Command]
    // 서버는 Cmd로 플레이어가 받아야될 데미지를 받으면 현재 hp에서 데미지를 빼고 남은 hp를 Rpc로 클라이언트에 알려준다.
    public void CmdDamaged(int dmg)
    {
        hp -= dmg;
    }

    void Update()
    {
        // 클라이언트에서 스페이스 버튼을 누르면 Cmd를 호출해서 서버에 플레이어가 입어야될 데미지를 알려준다.
        if (isClient && isLocalPlayer)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                CmdDamaged(10);
            }
        }
    }
}


위의 코드처럼 SyncVar의 어트리뷰트에 hook을 등록해두면 서버에서 hp값이 수정되고 그것이 클라이언트에 통지되었을 때, 자동으로 hook으로 등록해둔 함수가 호출된다. 즉, hp 값이 변경되었을 때, 클라이언트에서 추가적으로 처리해야 하는 일들을 처리할 수 있게 되는 것이다.


단, 이 hook을 사용할 때 알아두어야 할 것이 있다. hook은 서버에서 변경된 값이 클라이언트에 통지되었을때 동작하는 것임으로 클라이언트에서만 동작한다는 것과, hook을 사용한 경우에는 클라이언트의 멤버 변수에 동기화되어야하는 값이 바로 적용되지 않는다는 점이다. 멤버 변수에 바로 동기화되지 않고, hook으로 등록한 함수의 매개 변수를 통해서 전달되기 때문에, 클라이언트 측의 멤버 변수에 매개 변수의 값을 직접 대입해주어야 한다.

반응형
  1. HJ 2018.05.04 03:06

    hook 사용시 왜 클라이언트에 값이 바로 동기화 되지않는지 헤메고 있었는데 그 이유를 드디어 찾았네요!
    감사합니다 ^^!

반응형

UNet Tutorial (4) - Network Identity와 권한


지난 섹션에서 위치 동기화를 하면서, 게임 오브젝트에 Network Transform을 추가했을 때, Network Transform을 따라서 자동으로 추가된 컴포넌트를 기억하는가? 바로 Network Identity라는 컴포넌트다.




이름에서도 알 수 있듯이 이 컴포넌트는 오브젝트의 네트워크 ID를 관리하고 네트워킹 시스템에 알리는 역할을 한다. 즉, 이 오브젝트가 다른 서버나 클라이언트의 어느 오브젝트와 동기화되어야 하는지 알리는 역할을 한다

출처: http://wergia.tistory.com/99 [베르의 프로그래밍 노트]
이름에서도 알 수 있듯이 이 컴포넌트는 오브젝트의 네트워크 ID를 관리하고 네트워킹 시스템에 알리는 역할을 한다. 즉, 이 오브젝트가 다른 서버나 클라이언트의 어느 오브젝트와 동기화되어야 하는지 알리는 역할을 한다

출처: http://wergia.tistory.com/99 [베르의 프로그래밍 노트]
그것은 바로 Network Identity라는 컴포넌트인데, 이름에서도 알 수 있듯이 이 컴포넌트는 오브젝트의 네트워크 ID를 관리하고 네트워킹 시스템에 알리는 역할을 한다. 즉, 이 오브젝트가 다른 서버나 클라이언트의 어느 오브젝트와 동기화되어야 하는지 알리는 역할을 한다.

출처: http://wergia.tistory.com/99 [베르의 프로그래밍 노트]

지난 섹션에서도 언급했듯이 이 컴포넌트는 오브젝트의 네트워크 ID를 관리하고 네트워킹 시스템에 알리는 역할을 한다. 이 컴포넌트가 있어야 이 오브젝트가 다른 클라이언트의 어떤 오브젝트와 동기화 되어야 하는지 알 수 있는 것이다.


이번 섹션에서는 Inspector 뷰에서 표시되는 Server Only와 Local Player Authority라는 두 가지 옵션과 isServer, isClient, isLocalPlayer, hasAuthority라는 네 가지 프로퍼티에 대해서 알아볼 것이다.


우선은, Inspector 뷰에서 표시되는 두 가지 옵션에 대한 것이다.


Server Only - 여기에 체크된 오브젝트는 스폰될 때, 클라이언트에서는 스폰되지 않고 서버에서만 스폰된다.(네트워크 상에서의 스폰은 일반적인 Instantiate가 아닌 다른 방법을 통해서 스폰된다.)

Local Player Authority - 여기에 체크된 오브젝트는 이 오브젝트를 소유한 클라이언트가 오브젝트를 제어할 수 있다. 즉, 해당 클라이언트의 로컬 플레이어 오브젝트가 오브젝트에 대한 권한을 갖는다.


그 다음으로는, 네 가지 프로퍼티에 대한 것이다. 스크립트 상에서는 NetworkBehaviour를 상속받는 클래스의 멤버로 볼 수 있는데, 씬에서 스폰된 상태일 때는 Inspector 뷰 아래쪽에 Network Information으로 표시된다.



우선 설명할 것은 isClient와 isServer라는 프로퍼티이다. 이것은 간단히 말해서 지금의 네트워크가 서버이냐 아니면 클라이언트이냐를 알려주는 프로퍼티이다. 지난 섹션에서 본 Network Manager HUD를 이용하여 LAN Server Only(S)를 선택해서 서버를 열었다면 이 Network Identity의 isServer라는 프로퍼티는 true가 되는 것이고, LAN Client(C)를 선택해서 열린 서버에 접속했다면 isClient라는 프로퍼티가 true가 되는 것이다. 즉, 현재의 네트워크가 서버로서 다른 클라이언트의 접속을 받고 있는 것인지, 클라이언트로서 다른 서버에 접속하고 있는 것인지를 알 수 있다. 이것을 이용하면 서버에서만 동작하고 클라이언트에서는 동작하지 않는 방식의 코드를 작성할 수 있다.


일반적으로 이 isServer와 isClient는 둘 중에 하나만 true을 가지지만, LAN Host(H)를 선택하여 네트워크가 게임의 호스트가 되어서 다른 클라이언트들의 연결을 받게 되면 호스트 측에서는 이 두 프로퍼티 모두 true값을 가지게 되고, 호스트에 접속한 클라이언트들은 isClient 프로퍼티만 true값이 된다.

즉, isServer만 true라면 네트워크는 서버 역할만 맡고 있는 것이고, isClient만 true라면 클라이언트 역할만 맡고 있는 것이다. 그리고 isServer와 isClient가 모두 true라면 호스트로서 서버와 클라이언트의 역할을 동시에 하고 있는 것이다.


서버와 클라이언트가 분리된 방식은 일반적인 게임 서버나 데디케이트 서버에서 주로 사용되는 것이고, 하나의 클라이언트가 서버의 역할을 함께하는 호스트 방식은 주로 P2P 서버로 사용된다. 다만 P2P 서버 방식은 보안이나 해킹, 핵 등에 취약하다는 단점이 있다.





그 다음은 원래라면 HasAuthority 프로퍼티에 대한 설명을 할 차례지만 그전에 필요한 개념이 있기 때문에 isLocalPlayer에 대한 설명을 먼저 하겠다. 지난 섹션에서 클라이언트가 서버에 접속했을때, 이 클라이언트의 플레이어를 위한 하나의 프리팹을 Network Manager의 Player Prefab에 등록해준 것을 기억할 것이다. 이 플레이어 프리팹을 위해 사용되는 프로퍼티가 바로 isLocalPlayer이다.



플레이어 프리팹은 Auto Create Player의 설정을 통해서 하나의 클라이언트가 접속할 때마다, 자동으로 생성되거나, 클라이언트 측의 수동 플레이어 생성 요청을 받아 생성되는데, 이렇게 생성된 플레이어 오브젝트가 내 것인지를 알려주는 것이 바로 isLocalPlayer 프로퍼티이다. 클라이언트 측에서 작동할 코드를 만들때 그 클라이언트의 플레이어 오브젝트만 작동해야 하는 코드라면 isLocalPlayer 프로퍼티를 이용해 제한을 하는 것이 좋다.





그 다음은 HasAuthority 프로퍼티에 대한 내용을 알아보자. 이 프로퍼티는 이 네트워크 오브젝트의 권한을 가지고 있는가에 대한 프로퍼티이다.



위의 이미지에서는 Network Identity 컴포넌트의 Local Player Authority에 체크가 되어있는데, 이것이 체크되어 있지 않은 경우에는 그 오브젝트의 권한은 서버가 가지게 되고, 체크가 되어 있는 경우에는 위에서 설명한 isLocalPlayer가 true인 측에서 권한을 가지게 된다. isLocalPlayer가 아닌 쪽에도 이 권한을 줄 수 있지만, 그 방법에 대해서는 다른 섹션에서 설명하겠다.


이 hasAuthority 프로퍼티를 테스트하는 방법은 간단하다. 플레이어 프리팹에 Network Transform 컴포넌트를 추가하고 한 번은 Local Player Authority를 켜고, 또 한 번은 Local Player Authority를 끄고 움직여보자.


Local Player Authority를 켠 상태에서는 서버에서 아무리 플레이어 오브젝트를 움직여도 클라이언트 측으로 동기화 되지 않을 것이다. 하지만 그 플레이어 오브젝트를 isLocalPlayer로 가지는 클라이언트 측에서 플레이어 오브젝트를 움직이면 그 움직임이 서버와 다른 클라이언트에게 동기화 되는 것을 확인할 수 있다.


반대로 Local Player Authority를 끈 상태에서는 그 플레이어 오브젝트를 isLocalPlayer로 가지는 클라이언트는 물론 모든 클라이언트에서 움직여도 서버는 물론이고 다른 클라이언트로 동기화되지 않는다. 하지만 서버에서 플레이어 오브젝트를 움직이면 모든 클라이언트에 동기화된다.


네트워크 기능을 사용하는 커스텀 클래스를 만드는 경우, 위의 네 가지 프로퍼티들을 응용해서 각 상황에 따라 어떻게 동작하도록 할 것인지 정해줄 수 있다.


반응형
  1. 유넷린이 2018.03.08 12:23

    네트워크 매니저 HUD는 프로토타입 용도라서 정식게임에선 사용하기 어려운데, 커스텀할 방법에대해 강좌하실 생각은 없으신지요?
    나름 유튜브나 구글에서 검색해봐도 한글강좌는전무하고 그나마 영어로 진행되는건 또 너무 어렵군요 ㅜㅜ
    여건 상 어려우시다면 힌트나 좌표라도 주시면 고맙겠습니다.

    • wergia 2018.03.08 12:47 신고

      튜토리얼 차근차근 진행하면서 네트워크 매니저 커스텀에 대해서도 알려드릴 예정입니다 ^^

  2. 유넷린이 2018.03.09 10:27

    정말 고맙습니다. 강좌 기대하며 기다리겠습니다. (__)

  3. 2018.03.13 02:08

    비밀댓글입니다

    • 2018.04.02 16:15

      비밀댓글입니다

  4. tkdgur0298 2018.03.13 02:12

    잘봤습니다!.
    현재 HUD로 하는 버튼방식말구 스크립트로 연결해서 따로 따로 저가 만든 버튼으로 연결은 다됬습니다..
    허나 문제점은 여기서 끝나는게 아니더군요..ㅠㅠ
    첫째, 플레이어 프리팹은 여러개 등록이 불가능합니다. (이것을 스크립트를 수동으로 짤수있는지 여부가 궁금하네요 ㅠㅠ..자료가너무없어요)
    둘째, OnlineScne자체도 한개밖에 등록이안되니 메뉴 (비로그인) - > 스테이지(OnlineScnee) -> ?? 이렇게되는건데 , 방법이 없을런지요 ㅠㅠ
    유니티 공홈에도 너무 문서가없으니 3일동안 삽질 하고..도움이 너무 필요하네요!

반응형

UNet Tutorial (3) - 기초적인 위치 동기화 구현


Network Manager와 Network Manager HUD를 이용한 간단한 서버와 클라이언트 만들기


이번 섹션에서 우리는 유니티에서 제공하는 NetworkManager와 NetworkManager HUD, NetworkTransform 등을 이용해서 간단한 위치 동기화 구현을 만들어볼 것이다.



우선의 위의 화면과 같이 오브젝트의 위치를 파악할 수 있게 카메라가 바닥인 평면을 바라보게 만들자.



그 다음에는 게임 오브젝트를 하나 만들어서 Network Manager와 Network Manager HUD를 컴포넌트를 넣어주자. 여기서 Network Manager는 게임 내에서 네트워크 통신을 처리해줄 것이고, Network Manager HUD는 유니티 네트워킹 테스트용 UI를 제공한다.



게임 오브젝트에 Network Manager HUD를 추가하고 게임을 실행하면 위의 이미지처럼 몇 개의 버튼이 화면에 보이게 되고 보이는 버튼을 누르는 것 만으로 간단하게 서버를 열거나 열어놓은 서버에 접속할 수 있게 된다. 이전 섹션에서 설명한 것처럼 LAN Host 버튼을 누르면 클라이언트가 서버의 역할을 겸하게 되는 서버가 열리고, LAN Server Only 버튼을 누르면, 클라이언트의 기능은 배제된 서버만 열리게 된다. 그리고 LAN Client 버튼을 누르면 열린 서버에 접속하게 된다.





Network Transform을 이용한 위치 동기화 구현


위에서 Network Manager를 이용해서 서버를 열고 접속하는 방법을 확인했으니 이번에는 Network Transform를 이용해서 위치를 동기화하는 방법에 대해서 알아보자. 우선은 게임 씬에 큐브 오브젝트를 하나 만든 후에 아래의 이미지처럼 Network Transform을 컴포넌트에 추가해주자.



오브젝트에 Network Transform 컴포넌트를 추가하면 하나의 컴포넌트가 자동으로 생성된 것을 볼 수 있다.



그것은 바로 Network Identity라는 컴포넌트인데, 이름에서도 알 수 있듯이 이 컴포넌트는 오브젝트의 네트워크 ID를 관리하고 네트워킹 시스템에 알리는 역할을 한다. 즉, 이 오브젝트가 다른 서버나 클라이언트의 어느 오브젝트와 동기화되어야 하는지 알리는 역할을 한다. 자세한 옵션에 대해서는 이후에 설명할 것이다.



Network Transform 컴포넌트를 추가했다면, Hierachy 뷰에 있는 오브젝트를 Project 뷰로 드래그 앤 드롭해서 프리팹으로 만들자. 프리팹으로 만든 이후에는 Hierachy 뷰에 있는 오브젝트는 삭제해도 된다.



그 다음에는 아까 전에 만든 Network Manager의 항목 중에 Player Prefab이라는 곳에 방금 만든 TransformSyncObject를 넣어준다. 이 Player Prefab은 한 명의 플레이어가 접속했을 때마다 생성되는 프리팹을 의미한다.


여기까지 작업했다면 프로젝트를 빌드하고 서버를 에디터에서 켜고 빌드된 프로젝트를 통해 클라이언트로 접속해보자.



접속하면 우리가 만들어서 플레이어 프리팹으로 등록한 오브젝트가 생겨나고 서버 측 에디터의 뷰에서 피벗을 잡고 움직이면 클라이언트 측 화면에서도 함께 움직이는 것을 볼 수 있다. 다만, 서버에서 움직임과는 다르게 클라이언트의 움직임은 약간 끊어져서 보이게 되는데 이것은 여러가지 방법으로 해결할 수 있다.






첫 번째 방법으로는, Network Transform 옵션에서 Network Send Rate를 높여주는 방법이 있다. 이것을 올려주면 초당 위치 동기화 횟수를 늘려서 통신량이 많아지는 대신에 좀 더 부드러운 움직임이 가능하게 만들어 준다. 다만 Inspector 뷰에서 바꾸는 방법은 초당 29회가 한계로 더 짧은 주기로 동기화하기를 바란다면, 두 번째 방법으로, 별도의 커스텀 네트워크 트랜스폼을 만드는 것이 좋다. 또한 부드러운 움직임을 원하지만 통신량이 많지 않기를 원한다면 커스텀 네트워크 트랜스폼을 구현하고 보간이나 Lerp, 추측항법 등을 가미해야 한다.

반응형
  1. 빡겜택 2019.10.23 22:42 신고

    하나 더 질문하고자 합니다.
    저는 지금 2019.2.9f1 유니티를 사용해서 튜토리얼을 진행코자 하는데
    Network Manager Script가 나오지 않더군요.
    이것도 버전이 높아서 UNet 지원이 중단되서 안나오는건지요?

    Script에서 UnityEngine.Networking 할 때는 되는데 Add Component에서만 안되는건지 궁금합니다.

    • wergia 2019.10.24 10:02 신고

      Network Manager는 HLAPI에 포함되는 컴포넌트입니다. HLAPI는 2018.4부터 유니티와 함께 제공되지 않습니다.

+ Recent posts