반응형

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

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

반응형

기본적인 네트워크 전송량 최적화


일반적으로 게임을 제작할 때 최적화라는 요소는 매우 중요하다. 적당한 성능의 컴퓨터에서 훌륭한 퍼포먼스를 보여주는 것은 얼마나 좋은 일인가. 하지만 고사양의 컴퓨터에서도 모자란 퍼포먼스를 보여주게 된다면 그 게임은 유저들에게 상당한 비평을 받게 될 것이다. 그와 마찬가지로 멀티플레이를 지원하는 게임의 경우에는 네트워크 전송량의 최적화가 필요하다. 월정액으로 사용되는 국내 인터넷 환경상 PC 멀티플레이 게임의 경우에는 그 중요성이 조금은 덜하겠지만, 데이터 사용량에 따라 요금이 달라지는 환경이나, 매월 사용할 수 있는 데이터량이 제한되어 있는 3G/LTE 같은 경우에 게임 중에 네트워크 전송량이 너무 많다면 성능이 발적화된 게임만큼이나 많은 유저들의 불만을 불러올 것이 틀림이 없다.


최근에 간단한 네트워크 게임을 프로토타입으로 만들면서 네트워크 전송량을 거의 최적화 하지 않은 상태로 테스트를 한 적이 있었다. 그 때 10분간 진행된 게임으로 무려 24MB나 되는 데이터를 사용했었다. 실시간으로 많은 수의 캐릭터가 움직이는 게임인 것을 감안하더라도 상당히 많은 데이터 소모였다. 그렇다면 이번 섹션에서는 네트워크를 사용하는 게임이 데이터를 과식하지 않도록 네트워크 사용량을 다이어트 하는 방법에 대해서 알아보자.





너무 자주 전송하고 있지는 않은가?


첫 번째로 확인해보아야 할 것은 전송 빈도가 너무 짧지 않은가 하는 것이다.


using UnityEngine.Networking;

[NetworkSettings(channel = 0, sendInterval = 0.1f)]
public class Player : NetworkBehaviour
{
    // Player 클래스 코드
}


유니티 네트워크의 네트워크 매니저에 의해 관리되는 NetworkBehaviour를 상속받는 모든 클래스는 NetworkSetting이라는 어트리뷰트를 통해서 동기화나 원격액션을 보내는 채널과 전송 빈도를 설정할 수 있는데, 기본적으로 이 전송 빈도는 0.1초당 한 번씩 전송되게 되어 있다. 0.1초라는 기본값은 얼핏 보기에는 나쁘지 않은 빈도로 보이는데, 여기서 사람의 욕심이 모든 문제를 발생시킨다.


1초에 10번을 전송하는 것은, 캐릭터의 상태나 체력, 공격력 같은 스탯을 전송하는데에는 나쁘지 않은 값이지만, 위치 동기화에는 조금 부족해 보일 것이다. 유니티 네트워크에서 제공하는 기본 NetworkTransform 클래스를 사용해보면 기본 전송 빈도가 초당 9회로 설정되어 있는데, 테스트를 해보면 동기화를 해주는 측에서는 부드러운 움직임을 보이겠지만, 동기화를 받는 측에서는 움직임이 뚝뚝 끊어져서 보이게 될 것이다. 그리고 기본 NetworkTransform에는 최대 전송 빈도가 초당 29회로 제한되어 있는데 이것 역시 테스트를 해보면 움직임이 미세하게 끊어져서 보이는 것을 확인할 수 있다.


이러한 문제를 해결하기 위해서, 단순한 해결책을 동원하게 되는데, 그것이 바로 전송 빈도를 매우 짧게 잡는 것이다. 초당 30프레임을 맞추기 위해서 sendInterval을 0.03333초로 잡거나, 더 과한 욕심으로 초당 60프레임의 동기화를 하기 위해 0.01666초로 맞추게 되는 것이다.


단순하게 계산해봐도, 0.1초의 전송 빈도에 비해서 0.03333초는 3배, 0.01666초는 6배로 네트워크 전송량이 증가하게 되는 것이다. 일반적으로 위치 동기화에는 Vector3 타입의 변수를 사용하게 되는데 4byte인 float 타입의 x, y, z 3개의 변수가 들어 있으니 1회 전송에 최소 12byte가 사용되는 것이니, 초당 120byte(사실 이것도 많은 편이다)면 되는 것이 360byte, 1080바이트까지 늘어나게 되는 것이다. 무려 1초에 1KB나 되는 데이터를 소모하게 된다.


무작정 전송 빈도를 짧게 설정하는 것은 좋지 않은 선택이라는 것을 알 수 있다. 그리고 전송 빈도의 마지노선인 30프레임도 아무런 처리 없이는 움직임이 미세하게 끊어져 보이는 현상이 있다.


그렇다면 전송 빈도를 길게 하면서 움직임을 부드럽게 하는 방법은 무엇인가?





추측항법(Dead Reckoning)


첫 번째로 제시되는 방법은 추측항법이다. 추측항법이란 최근에 확인한 실제 위치에 현재 움직이는 방향과 속력, 즉 속도를 이용해서 현재 위치를 추정해서 움직이는 것이다. 서버에선 새로 동기화 되어야할 위치와 속도(움직이는 방향과 속도)를 전송해주고 클라이언트에선 위치를 적용한 뒤 다음 동기화가 오기 전까지 오브젝트를 속도에 맞춰 이동시켜야 한다.



이렇게 서버가 다음 위치를 보내주기 전까지 그 사이의 움직임을 클라이언트가 계산해서 처리해주기 때문에 비교적 전송되는 텀이 길더라도 부드러운 움직임을 구현할 수 있게 된다. 위의 그림을 보면 첫 번째 빨간 원이 서버로부터 동기화 받은 위치이고 화살표가 오브젝트가 이동하는 방향과 속력을 의미한다. 처음 위치와 이동할 방향과 속력을 알고 있다면 클라이언트는 오브젝트가 이동해야할 위치를 알 수 있기 때문에 동기화 신호가 오지 않은 구간에서 클라이언트가 오브젝트를 이동시켜서 부드럽게 움직이는 것처럼 보이게 만든다.


이 방법의 경우에는, 처음 위치와 속도가 정확하게 동기화되었다면, 서버와 클라이언트 양쪽에 존재하는 오브젝트의 위치가 매우 정확한 수준으로 동기화 될 수 있다는 장점이 있다.


하지만 단점도 있는데, 이 섹션이 네트워크 전송량 최적화라는 것을 생각해본다면, 일반 위치 동기화와 같은 전송 빈도라고 비교했을 때, 더 많은 데이터를 소모한다는 것이다. 일반 위치 동기화라면 위치, 즉 Vector3 하나만 전송하면 되지만, 추측항법은 위치와 속도, Vector3 두 개를 전송해야 한다. 데이터 소모가 2배로 늘어난다는 뜻이다. 그렇기 때문에 추측항법을 사용하기 위해서는 일반 위치 동기화를 사용할 때와 비교해서 적절한 수준의 전송 빈도를 잘 계산해서 사용해야만 한다.





보간법(Interpolation)


네트워크 전송량을 줄이면서 부드러운 움직임을 보이는 두 번째 방법은 보간법이다. 보간법이란 두 위치 사이의 비어있는 위치를 알고 있는 두 위치를 이용하여 채워넣는 것이다. 일반적으로 두 위치 사이를 직선으로 채워넣는 선형 보간법이 사용된다.


보간법의 경우, 다음 이동할 위치를 받은 즉시 오브젝트를 그 위치로 이동시키지 않고 그 다음 위치 동기화가 오는 시간동안 현재 위치에서 동기화 받은 위치로 Lerp를 통해서 이동시킨다. 이동시키는 도중이나 그 다음에 다음 위치가 동기화 된다면 다시 현재 있는 위치에서부터 새로 동기화 받은 위치를 향해서 Lerp를 시키는 것이다.


보간법은 일반 위치 동기화와 같이 동기화할 위치만 전송하면 되기 때문에 전송 빈도만 일반 위치 동기화보다 길게 잡으면 데이터 전송량이 쉽게 줄어든다는 장점이 있지만, 보간법의 경우에는 서버가 위치를 알려주면 클라이언트의 오브젝트가 그 위치를 뒤늦게 따라가는 방식이기 때문에 서버와 클라이언트 간의 오브젝트의 실제 위치가 차이가 발생할 수 있다.





불필요한 데이터를 전송하고 있지는 않은가?


전송 빈도 다음으로 살펴볼 것은 정말로 필요한 데이터만 전송하고 있는가다. 가장 많이 동기화 되는게 위치 동기화기 때문에 이번에도 예시는 위치 동기화를 위주로 하게 될 것이다.


위치 동기화의 경우에 Vector3를 기본으로 사용한다는 것은 앞의 파트에서도 이야기했었다. Vector3라면 x, y, z 값 float 3개를 가지기 때문에 최소 12바이트를 전송하게 되는 것도 이야기를 했다. 그렇다면 만약 게임이 높낮이 없이 평면 상에서만 움직이는 게임이라면 과연 Vecter3를 이용해서 x, y, z의 모든 좌표를 동기화해야만 하는 것일까? 아니다. 높이 값이 필요없다면 그것을 Vector3를 이용하지 않고 Vector2를 이용해서 전송하는 것만으로도 단순 계산으로 데이터 전송량의 33%를 감소시킬 수 있다.


using UnityEngine;
using UnityEngine.Networking;

public class Player : NetworkBehaviour
{
    [SyncVar(hook = "ChangePosVect3")]
    Vector3 posV3;
    void ChangePosVect3(Vector3 pos)
    {
        posV3 = pos;
        transform.position = posV3;
    }

    [SyncVar(hook = "ChangePosVect2")]
    Vector2 posV2;
    void ChangePosVect2(Vector2 pos)
    {
        posV2 = pos;
        transform.position = new Vector3(pos.x, 0f, pos.y);
    }

    public void SyncPos()
    {
        posV3 = transform.position;
        posV2 = new Vector2(transform.position.x, transform.position.z);
    }
}


위의 코드는 Vector3를 이용하여 위치 동기화를 할 때와 Vector2를 이용하여 위치 동기화를 할 때의 차이를 보여준다. 분명 Vector3를 이용하여 동기화를 할 때에 비해서 무언가 처리해야할 것이 늘어나는 것은 사실이지만, 네트워크 최적화라는 것이 원래 네트워크의 부담을 줄이기 위해 그 부담을 서버나 클라이언트로 옮기는 것이다.


33% 감소의 효율을 보여주는 Vector2를 이용하는 위치 동기화만으로는 아직 만족스럽지 못할 수도 있다. 그렇다면 보다 좀 더 극단적인 효율을 보여주는 부분이 있는데, 바로 Rotation 동기화다.


만약 탑뷰 시점의 캐릭터가 마우스 방향을 바라보는 게임을 만든다고 가정해보자. 일반적으로 로테이션 동기화에는 유니티에서는 Quaternion 타입이 사용되는데 Quaternion 타입은 x, y, z, w로 무려 float 4개로 한 번 동기화 하는데 16byte가 사용된다. 탑뷰 시점에서 캐릭터가 마우스 방향으로 바라본다고 하면 y축의 각도만 전송하면 된다는 것을 생각해봤을 때, 무려 12byte가 낭비되고 있는 것이다.


using UnityEngine;
using UnityEngine.Networking;

public class Player : NetworkBehaviour
{
    [SyncVar(hook = "ChangeRotQuat")]
    Quaternion rotQuat;
    void ChangeRotQuat(Quaternion rot)
    {
        rotQuat = rot;
        transform.rotation = rotQuat;
    }

    [SyncVar(hook = "ChangeRotfloat")]
    float rotY;
    void ChangeRotfloat(float rot)
    {
        rotY = rot;
        transform.rotation = Quaternion.Euler(0f, rotY, 0f);
    }

    public void SyncRot()
    {
        rotQuat = transform.rotation;
        rotY = transform.rotation.eulerAngles.y;
    }
}


Quaternion을 사용하는 로테이션 동기화를 float 하나로 변경하는 것만으로도 데이터 사용량을 75%를 줄일 수 있게 된다.


이렇게 네트워크 통신에서 필요하지 않은 데이터를 배제하는 것만으로도 상당한 양의 네트워크 전송량을 감소시킬 수 있다.





그 외의 방법


위에서 언급한 방법 이 외에도 여러 가지의 아이디어나 테크닉이 있을 수 있다. 간단하게 예를 들자면 좌표(좌표를 하나의 변수에 압축해서 넣을 경우에는 일정 수준의 정밀도를 포기해야 한다)나 캐릭터의 여러 스탯을 하나의 변수에 압축하여 전송한 뒤 다시 분할해서 사용하는 방법을 사용하려 전송량을 감소시킬 수 있다.

반응형
  1. su 2018.09.03 16:26

    안녕하세요 유니티 networkmanager가 같은 네트워크 대역끼리만 접속가능한가요? 외부 ip로 접속하고싶은데 방법을 잘 몰라서요.

    • wergia 2018.09.04 10:00 신고

      Unet은 멀티플레이 게임 네트워크를 위해서 만들어진 기능이기 때문에 외부 ip에서도 충분히 접속 가능합니다.

  2. su 2018.09.05 09:47

    답변 감사합니다. 리눅스 서버에서 서버기능만하는 거를 돌리고싶은데 어떻게 하는지 아시나요? 리눅스로 빌드를 했더니 x86_64 파일이 나와서 서버에서 실행했는데 잘 안되서요.

    • wergia 2018.09.06 10:37 신고

      어떤 방식으로 구현하셔서 빌드했는지를 모르니 어떠한 이유로 실행이 잘 안되는지 자세히 설명드리기 어렵습니다.

      일단 서버 세션의 경우에는 실행하면 자동으로 세팅하고 서버를 시작하고 네트워크 처리를 할 수 있게 플래그 같은 걸로 나눠서 코드를 작성해두셨나요?

  3. hoho 2018.11.30 20:55

    안녕하세요, 글 보면서 대단히 도움 많이 받고있어요!

    한가지 궁금한것이 있는데요,
    작성자님께서는 NetworkTransform 컴포넌트를 사용하지 않고 동기화하고 계신것으로 이해해도 되는걸까요?

    만약 그렇다면, unet자체의 networktransform 컴포넌트를 사용하는것에 비하여 어떤 장단점이 있을까요?

    예상해보기로는,
    단점은 직접 위치정보를 쏴주고 받아서 처리하는과정을 직접해주어야되는것이고,
    장점은 보간등의 커스텀 처리가 가능할 것 같은데요...

    저는 현재 networktransform을 사용하여 위치를 동기화하고있는데, 뚝뚝 끊어짐을 해결하기위해 보간처리를 커스텀하기가 어려워서요 ㅠㅠ

    궁금한 부분을 정리하자면..
    1. 보간을 적용하려면, 직접 보간개념을 적용한 위치동기화 전송과정을 구현해야하는것인가?
    2. 그게 아니라면 네트워크 트랜스폼 컴포넌트를 사용하면서 보간을 추가적으로 처리할수 있는가?

    입니다.

    도움이 절실합니다~ ㅠ_ㅠ


    • wergia 2018.11.30 21:41 신고

      위치동기화는 모든 게임에 똑같은 방식으로 만들어지는게 아니고 게임의 특성에 따라달라질 수 있는 것이라, 저는 UNet에서 제공하는 NetworkTransform은 잘 사용하지 않는 편입니다.

      커스텀 위치동기화 클래스를 만드신다면 클라이언트 측에서 보간하는 과정을 직접 구현하셔야 합니다.

      UNet에서 제공하는 Network Transform의 경우 자세히는 모르지만 Interpolate Move Factor라는 옵션이 있습니다. 이부분이 내간법을 이용해서 보간처리를 해주는 옵션으로 추측됩니다.

반응형

네트워크 메시지(Network Message)


지난 섹션들 중에선 Rpc와 Command, SyncVar와 Hook등을 통해서 서버와 클라이언트 사이에에서 통신을 하는 방법과 이것을 이용하기 위해 Network Behaviour를 가진 오브젝트를 스폰하는 방법을 알아보았다. 하지만 이러한 기능들은 유니티에서 정의한 네트워크 API 중에서도 높은 수준의 API(HLAPI)이고 특히 Network Server와 Network Client를 기반으로 작동하기 때문에 거기에 구애받는 제약사항이 존재한다.


Rpc와 Command 같은 원격 액션은 NetworkBehaviour에 속한다.


그 제약사항은 전 섹션인 클라이언트의 준비에서 다루었다시피 ClientRpc나 Command 같은 원격 액션과 멤버 변수 동기화를 담당하는 SyncVar와 Hook은 클라이언트가 준비되기 이전에는 동작하지 않는다는 것이다. 그렇기 때문에 클라이언트가 서버에 접속했을 때, 서버에서 클라이언트에 게임 진행에 필요한 데이터를 전송한다던가 하는 초기화 작업에는 원격 액션이나 SyncVar를 사용하지 않아야 한다.


그렇다면 클라이언트가 아직 준비되지 않은 상태에서는 어떤 방식으로 서버와 클라이언트가 통신을 해야하는가? 그 해답은 바로 네트워크 메시지(Network Message)를 사용하는 것이다. 네트워크 메시지는 위의 이미지를 보면 Network Server와 Network Client보다 Low level인 Tranport 레이어에 훨씬 가까운 것을 확인할 수 있다. 때문에 네트워크 메시지는 클라이언트가 준비 되어있는가에 대한 제약을 전혀 받지 않고 원하는 메시지를 주고 받을 수 있다.





기본적인 네트워크 메시지 전송


커스텀 네트워크 메시지 타입

public class CustomMsgType
{
    public static short YourMsgType = MsgType.Highest + 1;
}


유니티 네트워크에서 메시지를 전송하기 위해서는 몇 가지 절차가 필요한데, 첫 번째로는 당신이 보내고자하는 메시지의 타입이 무엇인지 정의하는 것이다. 유니티 네트워크에서는 기본적인 메시지를 시스템 내에서 전송하기 위해서 기본적인 메시지 타입을 정의하고 있는데, 이것은 MsgType 클래스에 상수로 정의되어 있다. 그 중에서 Highest라는 메시지 타입이 있는데 이것은 유니티가 사용하는 기본 메시지 타입 중 가장 마지막 타입을 의미하며, 이 이후의 타입은 개발자가 원하는 타입으로 정의해서 사용하면 된다는 뜻이다.


새로운 메시지를 사용할 때마다, MsgType.Highest에 +n하여 사용하면 되는데, 그냥 사용할 때마다 MsgType.Highest + n 으로 사용해도 되지만, 새로 정의한 메시지 타입의 가독성을 확보하기 위해서는 위의 예시 코드와 같이 별도의 메시지 타입 클래스를 만들고 메시지 타입 상수를 정의해서 대입해두는 것이 좋다.





유니티에서 지원하는 기본 네트워크 메시지


바로 위 파트에서는 전송하고자 하는 네트워크 메시지의 종류를 정의했다면, 이번에는 메시지의 내용을 채우는 것이다. 보내고자 하는 메시지가 단지 무언가가 되었다는 신호를 보내는 것일 수도 있지만, 일반적으로는 보내고자 하는 메시지의 내용이 있을 것이다. 이것을 위해서 유니티에서는 아주 기본적인 메시지 클래스를 제공하는데 그것은 다음과 같다.


using UnityEngine.Networking.NetworkSystem;


EmptyMessage   // 빈 메시지 아무 내용도 전송하지 않는다.
IntegerMessage  // int 를 전송하는 메시지
StringMessage    // string 을 전송하는 메시지


유니티 네트워크 내장 메시지 클래스를 사용하기 위해서는 UnityEngine.Networking.NetworkSystem을 사용해야 한다.





네트워크 메시지를 처리할 핸들러 등록하기


다음으로 할 작업은 개발자가 정의한 타입의 메시지를 처리할 핸들러를 만들고 등록하는 것이다. 기본적인 네트워크 메시지 핸들러 함수는 다음과 같이 작성하면 된다.


private void OnCustomMessageHandler(NetworkMessage netMsg)
{
}


이렇게 만들어진 핸들러를 네트워크 매니저에 등록해야하는데, 이 과정은 서버와 클라이언트가 비슷하지만 약간 차이가 있다.


public override void OnStartServer()
{
    base.OnStartServer();

    NetworkServer.RegisterHandler(CustomMsgType.YourMsgType, OnCustomMessageHandler);
}


서버에서 핸들러를 등록할때는 NetworkServer 클래스의 정적 함수를 통해서 메시지의 타입과 핸들러 함수를 전달해주면 된다.


public override void OnStartClient(NetworkClient client)
{
    base.OnStartClient(client);

    client.RegisterHandler(CustomMsgType.YourMsgType, OnCustomMessageHandler);
}


클라이언트에서는 서버와는 다르게 클라이언트가 서버에 연결될 때, 네트워크 매니저에서 생성해서 전달해주는 NetworkClient 객체에 핸들러를 등록해야 한다.


서버에서만 받을 메시지라면 서버에만 핸들러를 등록하면 되고, 클라이언트에서만 받을 메시지라면 클라이언트에만 핸들러를 등록하면 된다.





네트워크 메시지 전송하기


앞에서 커스텀 메시지 타입을 만들고, 메시지 핸들러를 등록했다면 이번에는 기본적인 메시지를 전송해보자.


우선은 서버에서 클라이언트로 메시지를 전송하는 방법이다.


public override void OnServerConnect(NetworkConnection conn)
{
    base.OnServerConnect(conn);

    EmptyMessage msg = new EmptyMessage();
    conn.Send(CustomMsgType.YourMsgType, msg);
}


서버에서 클라이언트로 메시지를 전송하는 방법의 첫 번째는 서버 콜백의 매개변수로 전달되는 클라이언트의 네트워크 커넥션에 있는 Send() 함수로 메시지를 전달하는 방법이다. 이 방법은 콜백을 보낸 클라이언트에게 콜백에 대한 처리를 한 뒤에 메시지를 보낼 때 주로 사용된다.


Dictionary<string, NetworkIdentity> playerList = new Dictionary<string, NetworkIdentity>();

public void SomeSendMessageToClientProcess(string id)
{
    EmptyMessage msg = new EmptyMessage();
    playerList[id].connectionToClient.Send(CustomMsgType.YourMsgType, msg);
}


두 번째 방법은 플레이어 객체에 있는 NetworkConnect의 Send() 함수를 호출해서 메시지를 전송하는 방법이 있다. 두 번째 방법의 경우에는 원하는 클라이언트에 메시지를 보낼 수 있다는 장점이 있다. 단, connectionToClient는 서버 측에 있는 플레이어 객체에서만 유효하다. 즉, 클라이언트 측이나, 서버라도 플레이어 객체가 아닌 네트워크 객체에서는 connectionToClient가 null 값을 가지고 있기 때문에 그 connection에 메시지를 보낼 수 없다. 때문에 플레이어 객체가 만들어질 때, 네트워크 매니저에 만들어둔 하나의 컨테이너에 방금 만들어진 플레이어 객체의 Network Identity나, 플레이어 객체의 컴포넌트인 Network Behaviour를 등록해두고 빠르게 찾을 수 있도록 만들어 주는 것이 좋다.


다음은 반대로 클라이언트에서 서버로 메시지를 전송하는 방법이다. 이것 역시 서버로 메시지를 보내는 경로는 2가지 정도가 있다.


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

    EmptyMessage msg = new EmptyMessage();
    conn.Send(CustomMsgType.YourMsgType, msg);
}


네트워크 매니저 콜백 섹션에서 이야기 했던 것과 같이 클라이언트 콜백의 매개변수로 넘어오는 Network Connection에는 연결된 서버에 대한 정보가 담겨있기 때문에 이 매개변수의 Send를 통해서 서버에 메시지를 보낼 수 있다.


public void SomeSendMessageToServerProcess()
{
    EmptyMessage msg = new EmptyMessage();

    client.Send(CustomMsgType.YourMsgType, msg);
}


서버로 메시지를 보내는 두 번째 방법은, 네트워크 매니저 클래스의 멤버 변수인 client를 통해서 Send() 함수를 호출하는 것이다.


서버에서 클라이언트로, 클라이언트에서 서버로 메시지를 보내는 방법을 알아보았는데, 클라이언트 간의 메시지 전송 방법에 대해서 궁금할 수도 있다. 하지만 서버를 구현할 때는, 보안과 안정성을 위해서 모든 메시지가 서버를 거쳐가도록 하는 것이 좋다. 만약 클라이언트에서 다른 클라이언트로 메시지를 보내고 싶다면, 메시지를 보내고자 하는 클라이언트에서 서버에 메시지를 보낸 뒤 서버가 그 메시지를 받아야 하는 클라이언트로 메시지를 보내도록 하는 중계 방식으로 구현하는 것이 좋다.





커스텀 메시지


기본적인 메시지 클래스를 이용해서 메시지를 주고 받는 방법을 확인했으니 이번에는 전송하고자 하는 메시지 내용에 맞는 커스텀 메시지를 만들고, 전송하는 법을 알아보자.


public class CustomMessage : MessageBase
{
    public int i;
    public float f;
    public string str;
}


커스텀 메시지 클래스를 구현하는 방법은 위의 예시 코드와 같이 MessageBase를 상속받아서 메시지 클래스를 구현하면 된다. 메시지 클래스에는 기본적인 값 타입의 변수와, 구조체, 배열과 Vector3와 같은 대부분의 일반 Unity Engine 타입인 멤버를 포함할 수 있고, 복잡한 클래스나 제네릭 컨테이너는 멤버로 포함할 수 없다.


public void SomeSendMessageProcess(NetworkConnection conn)
{
    CustomMessage msg = new CustomMessage()
    {
        i = 10,
        f = 3.14f,
        str = "Hello"
    };

    conn.Send(CustomMsgType.YourMsgType, msg);
}


커스텀 메시지를 전송하는 법은 매우 간단하다. 객체를 생성하고 앞 파트에서 배운 방식대로 그 메시지를 그대로 전송하면 된다.


private void OnCustomMessageHandler(NetworkMessage netMsg)
{
    var msg = netMsg.ReadMessage<CustomMessage>();


    Debug.Log(msg.i);
    Debug.Log(msg.f);
    Debug.Log(msg.str);
}


그리고 메시지를 받는 쪽에서는 메시지가 NetworkMessage 타입의 객체로 전달되는데 이것을 ReadMessage<>() 함수로 원래 타입의 메시지 객체로 변환해서 사용하면 된다.

반응형
반응형

클라이언트의 준비(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);
}



반응형
반응형

UNet Tutorial (5) - Command와 Client Rpc


유니티 네트워크에서는 클라이언트와 서버 간에 액션을 수행하는 방법이 있다. 이를 원격 프로시저 호출(RPC)라고 하는데 유니티 네트워크 시스템에는 두 가지 타입의 RPC가 있다. 클라이언트에서 호출되어서 서버에서 수행되는 커맨드(Command)와 서버에서 호출되어서 클라이언트에서 수행되는 클라이언트 RPC(Client Rpc)가 그것이다.


아래의 다이어그램을 통해서 유니티 네트워크에서 원격 액션이 동작하는 방향을 확인할 수 있다.






커맨드(Command)


커맨드는 클라이언트의 플레이어 오브젝트에서 서버의 플레이어 오브젝트로 보내진다. 커맨드는 클라이언트 자신의 플레이어 오브젝트에서만 보낼 수 있으며 타 클라이언트가 소유한 플레이어 오브젝트를 통해서는 보낼 수 없다. 또한 일반적으로 플레이어 오브젝트가 아닌 네트워크 오브젝트에서도 커맨드를 보낼 수 없지만 5.2 버전부터 플레이어가 없는 오브젝트(클라이언트 권한 있음)에서도 커맨드를 보낼 수 있다. 이러한 오브젝트는 NetworkServer.SpawnWithClientAuthority를 통해 스폰되거나 NetworkIdentity.AssignClientAuthority를 통해 권한을 설정한 상태여야 한다. 이러한 오브젝트에서 전송한 커맨드는 오브젝트의 서버 인스턴스에서 실행되고, 클라이언트에서의 해당 플레이어 오브젝트에서 실행되지 않는다.


함수를 커맨드로 바꾸려면 [Command] 커스텀 속성을 사용하고 함수의 이름에 "Cmd" 접두사를 붙여주어야 한다.


using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class Player : NetworkBehaviour
{
    int count = 0;

    [Command]
    public void CmdTestFunction(int i)
    {
        Debug.Log("Command Function Call. " + i);
    }

    void Update ()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            CmdTestFunction(count++);

            Debug.Log("Input A. " + count);
        }
    }
}



서버와 클라이언트를 실행하고 클라이언트 측에서 'A'를 입력하면 커맨드가 호출되서 서버에서 수행되는 것을 확인할 수 있다.





클라이언트 Rpc(Client Rpc)


클라이언트 Rpc는 커맨드와 반대로 서버의 오브젝트에서 호출되어서 클라이언트의 오브젝트에서 수행된다. 서버의 경우에는 모든 권한을 가지고 있기 때문에 Network Identity를 포함해서 모든 서버의 오브젝트에서 이 호출을 보낼 수 있고 이것은 보안 상의 문제가 되지 않는다. 함수를 ClientRpc 호출로 만들기 위해서는 커맨드와 같이 [ClientRpc] 커스텀 속성을 함수 앞에 추가하고 함수 이름에 "Rpc"라는 접두사를 추가해야 한다.


using UnityEngine;
using UnityEngine.Networking;

public class Player : NetworkBehaviour
{
    int count = 0;

    [ClientRpc]
    public void RpcPlayerConnected()
    {
        Debug.Log("Client Rpc Call.");
    }

    [Command]
    public void CmdTestFunction(int i)
    {
        Debug.Log("Command Function Call. " + i);
    }

    void Update ()
    {
        if(Input.GetKeyDown(KeyCode.A))
        {
            CmdTestFunction(count++);
            Debug.Log("Input A. " + count);
        }
    }
}


using UnityEngine;
using UnityEngine.Networking;

public class CustomNetworkManager : NetworkManager
{
    public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
    {
        base.OnServerAddPlayer(conn, playerControllerId);

        Debug.Log("Add Player.");

        var players = FindObjectsOfType<Player>();
        foreach(var player in players)
        {
            player.RpcPlayerConnected();
        }
    }
}


위의 예제 코드는 서버에 새로운 클라이언트가 접속해서 새 플레이어 오브젝트가 생성되었을 때, 서버에 접속된 모든 클라이언트의 플레이어 오브젝트에 알려주는 역할을 하는 코드이다.







원격 액션에서의 매개변수


커맨드와 클라이언트 Rpc의 호출로 전달되는 매개변수는 직렬화되어 네트워크로 전송된다. 매개변수로 전달가능한 타입은 다음과 같다.

  • 기본 타입(byte, int, float, string, UInt64 등)
  • 기본 타입 배열
  • 허용 가능 타입을 포함하는 구조체
  • 빌트인 Unity 수학 타입(Vector3, Quaternion 등)
  • NetworkIdentity
  • NetworkInstanceId
  • NetworkHash128
  • NetworkIdentity 컴포넌트가 부착된 게임 오브젝트

원격 액션으로 전달되는 매개변수는 스크립트 인스턴스나 트랜스폼과 같이 게임 오브젝트의 하위 요소여서는 안된다. 직렬화되어 네트워크로 보내질 수 없는 타입도 매개변수가 될 수 없다.

반응형
반응형

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부터 유니티와 함께 제공되지 않습니다.

반응형

UNet Tutorial (1) - Overview


네트워크 게임을 제작할 때, 클라이언트의 네트워크를 구현하는 방법은 여러가지가 있다. 대표적인 방법으로는 포톤 클라우드가 제공하는 Photon Unity Networking과 넷텐션의 ProudNet으로 구현하는 방법등이 있다. 이번 포스팅에서는 이런 외부에서 제공하는 방식이 아니라 유니티가 제공하는 Unity Networking을 이용하는 방법에 대해서 알아보게될 것이다.


사실 이전까지는 유니티에서 제공하는 Networking은 잦은 버그와 잘알려지지 않은 점, 사용 예가 많지 않은 점등을 이유로 잘 사용되지 않은 편인데, 계속된 버전업과 지난 2017년 12월 04일에 유니티 테크놀러지 측에서 게임 서버 호스팅 업체를 인수한 것으로 충분히 적용해 볼만 하다고 여겨져서 튜토리얼을 작성하게 되었다.





UNet의 구조


 

유니티의 네트워킹 API는 위의 이미지와 같은 레이어로 이루어져 있는데 두 가지 방식으로 사용할 수 있다.


1. 유니티에서 제공하는 "High-Level" 스크립팅 API - HLAPI라고 부르며, 이것을 이용하여 네트워킹 기능을 구현할 경우 Low-Level의 세부 사항에 대해 신경쓰지 않고도 멀티 유저 게임에 일반적으로 필요한 대부분의 요구 사항을 만족시킬 수 있는 커맨드에 접근할 수 있다.

2. Low-Level의 전송 레이어(Transport Layer) API - 네트워크 인프라 또는 커스텀화된 고급 멀티 플레이어 기능을 구현하고자 할 경우, 이 API를 통해 저수준에서 스스로의 네트워킹 시스템을 빌드할 수 있으며, 이는 게임 네트워킹에 있어서 구체적이거나 고급 요구 사항이 있는 경우 유용하다.


위의 두 가지 API를 통해서 개발자는 저수준에서 고수준까지 원하는 방법으로 게임의 네트워크를 구현할 수 있게 된다.




Unity가 제공하는 서비스


이러한 API 이 외에 유니티는 Unity Multiplayer라는 서비스를 통해서 다음과 같은 기능들을


  • 매치메이킹 서비스
  • 매치를 생성하고 홍보.
  • 참여 가능한 매치 리스트를 제공하고 매치에 참여.
  • 릴레이 서버
  • 전용 서버 없이 인터넷을 통한 게임플레이.
  • 매치 참여자에게 메시지 전송.

단, 이 Multiplayer 서비스는 유료 서비스로 이용하게 될 경우 별도의 비용을 지불해야 한다.


다음 섹션에서는 UNet의 몇몇 개념과 간단한 구현 튜토리얼을 알아보게 될 것이다.

반응형
  1. 육체피로 2018.03.09 17:48

    안녕하세요.
    유니티 네트워킹 기능 보다 문제가 좀 있어서 관뒀었는데, 지금은 어떤지 궁금합니다.

    당시 있던 이슈가
    일단 LAN 에서만 동작되고, (홀펀칭등의 이슈가 발생합니다)
    매치메이킹 서비스를 이용할 경우 비용이 발생합니다.
    해당 이슈를 해결하기 위한 Asset들이 몇개 있었는데,
    포톤의 경우 가장 호환성이 좋긴 하지만 CCU 별 비용을 지불해야 하고,
    그외 라이브러리들은 모바일 지원을 하지 않거나 안정성등에 문제가 있었습니다.

    5.5 버전때 본거라 최신 버전에서 변동이 있는지 궁금합니다.
    좋은 하루 되세요~

  2. 빡겜택 2019.10.21 19:19 신고

    글을 읽다가 궁금해서 남깁니다.

    혹시 그러면 유료 버전을 사용하지 않으면 UNet을 이용할 수 없는 것인가요?

    저는 이 강의를 보면서 UNet을 간략하게나마 이해하고자 해서 여쭤봅니다.

    • wergia 2019.10.22 10:43 신고

      유료 버전을 사용하지 않더라도 UNet을 사용할 수 있습니다. 다만 유니티에서 추후에 UNet 지원을 중단하고 차세대 네트워크 API를 새로 제공할 예정입니다. 해당 내용은 다음 링크에서 확인하실 수 있습니다.

      https://wergia.tistory.com/185

+ Recent posts