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

 

일반적으로 게임을 제작할 때 최적화라는 요소는 매우 중요하다. 적당한 성능의 컴퓨터에서 훌륭한 퍼포먼스를 보여주는 것은 얼마나 좋은 일인가. 하지만 고사양의 컴퓨터에서도 모자란 퍼포먼스를 보여주게 된다면 그 게임은 유저들에게 상당한 비평을 받게 될 것이다. 그와 마찬가지로 멀티플레이를 지원하는 게임의 경우에는 네트워크 전송량의 최적화가 필요하다. 월정액으로 사용되는 국내 인터넷 환경상 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%를 줄일 수 있게 된다.

 

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

 

 

 

 

그 외의 방법

 

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

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

+ Recent posts