유니티에서 프라우드넷으로 네트워크를 구축할 때 위치동기화를 위한 추측항법 사용법


이 문서를 보기전에 필요한 지식 :

1. 프라우드넷 서버와 클라이언트 구축법.

2. 프라우드넷의 네트워크 전송 방식인 ReliableSend와 UnreliableSend의 차이점

3. 프라우드넷의 RMI, Proxy와 Stub에 대한 지식.

4. 프라우드넷을 통한 게임 네트워크에서 캐릭터 위치 동기화 하는 방법.

5. 유니티 프로젝트에서 프라우드넷을 사용하는 방법.



1. 캐릭터의 위치 동기화란

캐릭터가 움직일 수 있는 네트워크 게임에서 캐릭터의 위치란 아주 중요한 것이다. 상대방 캐릭터의 실제 위치가 내가 인식한 위치가 다르다면 문제가 될 것이다. 다음의 그림을 보자 : 


만약 상대방 캐릭터의 위치 동기화에 실패했다고 가정해보자. 파란색 원이 내 캐릭터이고 분홍색 원은 상대 캐릭터의 실제 위치이며 빨간 원은 위치동기화에 실패해서 상대 캐릭터가 잘못 이동한 위치라고 했을 때, 내가 보이는 대로 빨간색 적을 공격한다면 데미지가 적에게 들어가야할까? 내 입장에서 보이는대로 빨간색 상대를 공격해서 데미지가 들어간다면 나는 납득할 수 있을지 모르겠지만 상대방의 입장에서는 납득하기 어려울 것이다. 상대방이 보기에는 내가 다른 방향을 보고 공격하는데 자신한테 데미지가 들어오는 것으로 보일 것이기 때문이다.


이렇듯이 캐릭터의 위치 동기화는 중요하다. 여기에 자주 사용되는 방법은 자신의 캐릭터의 위치가 변경될 때마다 상대방의 클라이언트에 그 변경된 위치를 알려주는 것이다.


캐릭터의 위치를 다른 클라이언트에게 전송하는 Proxy

    public void SyncUnitPos(string unitName, UnityEngine.Vector3 pos, Quaternion rot)
    {
        RmiContext context = RmiContext.UnreliableSend;

        client.c2cMessageSender.SyncUnitPos(otherClientHostID, context, unitName, pos, rot);
    }


다른 클라이언트로부터 받은 캐릭터의 위치를 자신의 클라이언트에서 찾아서 적용하는 Stub

client.c2cMessageReceiver.SyncUnitMove = (HostID remote, RmiContext rmiContext, string unitName, UnityEngine.Vector3 pos, Quaternion rot) =>
{

Character moveCharacter = AllSpawnUnitDictionary[unitName];

moveCharacter.transform.position = pos;

moveCharacter.transfrom.rotation = rot;

};


위의 프록시와 스텁으로 단순하게 캐릭터의 위치를 보내고 받아서 적용하는 것이다. 이 방법은 단순하지만 중요한 단점이 있다. 그것은 바로 여러가지 상황에 따라 캐릭터의 위치를 전송받는 측에서의 캐릭터의 움직임이 끊어져 보일 수 있다는 것이다. 특히 캐릭터의 위치 동기화처럼 네트워크 전송량이 많은 것을 처리할 때는 호스트 간에 메시지를 주고받는 시간이 짧은 UnreliableSend를 사용하기 때문에 메시지의 도착의 확실성이 보장되지 않기 때문에 중간에 잃어버리는 메시지가 많다면 캐릭터의 움직임이 끊어져서 버벅거리면서 이동하는 것처럼 보이거나 텔레포트하는 것처럼 보일 확률이 높아진다.



2. 추측항법이란

이것을 해결하기 위한 방법이 바로 추측항법이다. 추측항법의 사전적 의미는 다음과 같다 :


가장 최근에 구한 정확한 선위를 기초로 선박의 침로와 항정에 의하여 현재의 선위를 추측 및 추정하거나 또는 출발지와 목적지의 경 · 위도를 알고 앞으로 취해야 할 침로 및 항정을 구하는 항법을 추측 항법이라고 한다. 선박이 항행 중에 가장 필요로 하는 것이 바로 선박의 현재 위치이다. 선박의 실측 위치를 얻을 수 없을 때에는, 가장 최근에 실제로 관측하여 구한 실측 위치를 기준으로 하여 그 후에 조타한 진침로와 선속 또는 기관의 회전수로 구한 항정에 의하여 선위를 결정하는 것을 선위의 추측이라 하고, 이와 같이 하여 결정된 선위를 추측위치라 한다.


[네이버 지식백과] 추측항법 [dead reckoning sailing, 推測航法] (선박항해용어사전, 한국해양대학교)


간단하게 말하면 가장 최근에 확인한 실제 위치를 기준으로 하여 현재의 속도와 방향을 더하여 현재 위치를 추정해내는 것이다. 실제로는 자신의 위치를 특정하기 어려운 항해에서 사용되는 방법인데 이것을 네트워크 게임의 개발에 응용한 것이다. 개념은 기존의 캐릭터의 위치와 방향을 가져와서 바로 적용하는 것에 더해서 캐릭터의 이동 방향과 속도를 함께 가져와서 다음 캐릭터 위치 동기화 신호가 오기전까지 받은 이동 방향과 속도를 이용해서 추정해낸 위치로 미리 캐릭터를 이동시키는 것이다.


우리는 이제 추측항법을 구현해야 하겠지만 다행이도 이러한 기능을 구현하는데 도움을 주는 클래스가 이미 프라우드넷에 구현이 되어있다. Nettention.Proud.PositionFollower 라는 클래스이다.


그리고 캐릭터의 위치를 동기화 하는 Proxy와 Stub은 다음과 같이 변경될 것이다.


캐릭터의 위치를 다른 클라이언트에게 전송하는 Proxy

    public void SyncUnitPos(string unitName, UnityEngine.Vector3 pos, UnityEngine.Vector3 vel, Quaternion rot)
    {
        RmiContext context = RmiContext.UnreliableSend;

        client.c2cMessageSender.SyncUnitPos(otherClientHostID, context, unitName, pos, vel, rot);
    }


다른 클라이언트로부터 받은 캐릭터의 위치를 자신의 클라이언트에서 찾아서 적용하는 Stub

client.c2cMessageReceiver.SyncUnitMove = (HostID remote, RmiContext rmiContext, string unitName, UnityEngine.Vector3 pos,  UnityEngine.Vector3 vel, Quaternion rot) =>
{

Character moveCharacter = AllSpawnUnitDictionary[unitName];
moveCharacter.MoveRemote(pos, vel, rot);

};


그냥 pos와 rot을 넣어주기만 하면 되던 이전과는 달리 처리할 것이 많으니 Character 클래스에 MoveRemote라는 함수를 만들었다.


Character 클래스에서 처리할 것

public class Character

{

// 이 캐릭터가 네트워크를 통해서 간접적으로 조종되는 캐릭터인가?

bool isRemote;

// 추측 항법을 도와줄 프라우드넷의 클래스이다.

Nettention.Proud.PositionFollower posFollower = new Nettention.Proud.PositionFollower();


// 네트워크의 stub을 받아 Follower의 위치를 설정한다

public void MoveRemote(Vector3 pos, Vector3 vel, Quaternion rot)

{

var npos = new Nettention.Proud.Vector3();
npos.x = pos.x;
npos.y = pos.y;
npos.z = pos.z;
      
var nvel = new Nettention.Proud.Vector3();
nvel.x = velocity.x;
nvel.y = velocity.y;
nvel.z = velocity.z;


posFollower.SetTarget(npos, nvel);

transform.rotation = rot;

}


// Follower에 따라서 캐릭터를 이동시킨다

public void FollowRemoteMove()

{

posfollower.FrameMove(Time.deltaTime);

var pos = new Nettention.Proud.Vector3();

var vel = new Nettention.Proud.Vector3();

posfollower.GetFollower(ref pos, ref vel);
unit.transform.position = new Vector3((float)pos.x, (float)pos.y, (float)pos.z);
unit.transform.rotation = Quaternion.Lerp(unit.transform.rotation, destRot, Time.deltaTime * 100);

}


void Update()

{

if (isRemote) FollowRemoteMove();

}

}


이 방법을 사용할 때는 자신의 캐릭터와 상대방의 캐릭터, 즉 직접 조종하는 캐릭터와 네트워크를 통해서 조종되는 캐릭터를 명확하게 구분해야 한다.

반응형
  1. 팩맨 2017.08.07 13:38

    도움받고 갑니다 감사합니다. ^^

편리한 PIDL 컴파일을 위한 PIDL 애드온 사용하기


넷텐션의 공식 문서에도 설명되어 있다시피 프라우드넷에서는 네트워크 프로그램을 제작시 개발자를 힘들게 하는 프로그래밍 작업 (메시지 구조체 정의, 송신 함수, 수신 함수 작성)을 함수 호출의 형태로 간소화시키고 작업자의 실수를 줄이기 위해서 RMI(Remote Method Invacation, 원격 메서드 호출)를 사용한다. 프라우드넷에서 RMI를 사용하기 위해서는 PIDL 파일을 작성하고 컴파일해야 한다. 그리고 이 PIDL을 컴파일하기 위해서는 우선 몇 가지 설정을 해주어야 하는데 이 과정이 상당히 복잡하고, 새로운 프로젝트를 생성할 때마다 설정을 하기는 매우 번거롭고 귀찮은 작업이 된다.



위의 보기처럼 작성한 PIDL 파일의 속성을 열고 복잡한 커맨드를 입력해주어야 한다. 그리고 새로 만들어지는 PIDL 파일에도 일일이 입력해주어야 한다. 하지만 넷텐션에서는 이미 이 귀찮은 작업을 해결해줄 애드온을 만들어두었다.


경로 :: ...\Nettention\ProudNet\util



위의 경로를 찾아보면 다음과 같이 PIDL-addon.vsix라는 파일이 존재하는데, 이것을 실행하면 다음과 같이 PIDL 애드온을 설치할 수 있다 :





이 애드온을 설치하고 나면 위의 그림과 같이 비주얼 스튜디오에서 PIDL의 파일의 아이콘이 PIDL 글자가 박힌 아이콘이 변경되고, 달리 PIDL 파일의 속성에서 PIDL 컴파일 설정을 해주지 않아도 PIDL 파일을 컴파일할 수 있게 된다.


반응형

ProudNet 다운로드 및 설치하기

프라우드넷을 설치하기 위해서는 우선 프라우드넷의 라이선스를 받아야한다.


http://proudnet.com/proud-net


위 주소에서 라이선스를 받을 수 있다.



퍼스널 라이선스의 경우는 20명의 접속자 제한을 제외하고는 다른 제한 없이 공부용으로 충분히 사용할 수 있다.



퍼스널 라이선스에 대한 약관에 동의하고 정보를 채워서 신청을 끝내면 입력한 이메일 주소로 라이선스의 키와 프라우드넷 게임 엔진을 다운로드 받을 수 있는 링크가 포함된 메일이 날아온다.




위의 링크를 클릭하면 프라우드넷을 다운로드 받을 수 있는 넷텐션 개발자 네트워크에 접속할 수 있다.


https://ndn.nettention.com/login/



라이선스를 받을 때 입력했던 아이디와 비밀번호를 입력하면 접속할 수 있다.




접속하면 다운로드 가능한 프라우드넷 각 버전들의 목록을 볼 수 있다. 이 중에 원하는 버전을 선택해서 다운로드받아서 설치하면 된다.



설치 과정 중에는 다음과 같이 라이선스 키를 입력하는 것이 있는데, 이것은 제일 처음 라이선스 신청을 하고 받은 메일에서 복사해와서 붙여넣어주면 된다.


그렇게 하면 프라우드넷의 설치를 마칠 수 있다.


반응형

P2P 게임 네트워크 구현시 슈퍼 피어 선정하기



게임 네트워크를 제작할 때, 대다수의 게임이 서버와 통신을 하지만 몇몇 게임의 경우 로그인이나 게임의 데이터 송수신은 서버와 하지만 네트워크 게임 플레이 자체는 클라이언트 간의 통신, 즉 peer-to-peer(이하 P2P) 방식으로 처리하는 경우가 있다. 일반적으로 소규모 멀티 플레이 게임이나 데이터 전송량이 많은 FPS 같은 게임의 멀티 플레이에서 P2P 방식을 사용할 것을 권장한다.


이러한 소규모 온라인 게임(Multi Online, MO)에서 사용하는 P2P 통신 방법의 하나로, P2P 그룹 내에 있는 멤버 중 하나가 게임 플레이를 위한 메시지의 송수신을 담당하는 Super peer(슈퍼 피어 혹은 호스트) 중심의 P2P 네트워킹이 있다.


간단한 예를 들자면 FPS 게임에서 P2P 네트워킹을 사용하는데 게임의 종료 조건 중의 하나가 타임 오버일 때, 그 P2P 그룹에 속하는 모든 클라이언트가 각자의 시간을 가지고 계산한다면 상황에 따라서 게임이 종료되는 시간이 각 클라이언트마다 제각각인 상황이 발생할 수도 있다. 이러한 상황을 방지하기 위해 P2P 그룹의 멤버들 중에서 하나의 슈퍼 피어를 선정하고 그 슈퍼 피어인 멤버의 클라이언트가 게임의 시간을 관리하게 하는 것이다. 그렇게 함으로써 모든 클라이언트의 종료 시점은 동일해질 수 있다.


이런 슈퍼 피어를 선정할 때에도 고려해야할 조건들이 있는데, 그것은 바로 P2P 그룹의 멤버 중에서 가장 네트워크 접속 상태가 좋고 전송 속도가 빠른 멤버를 슈퍼 피어로 삼아야 한다는 것이다. 만약에 통신 품질이 좋지 않은 클라이언트를 슈퍼 피어로 선정하게 되면 게이밍 품질이 하락할 수 있다. 예를 들자면 FPS 게임에서 캐릭터의 이동을 슈퍼 피어의 기준으로 동기화하였는데, 슈퍼 피어의 상태가 좋지 않다면 캐릭터의 이동이 뚝뚝 끊어져 보일 것이다.


슈퍼 피어를 선택하는 방법은 다음과 같다 :


1. 인터넷 공유기 뒤에 있지 않고 직접 회선에 물려있는 경우 슈퍼 피어로서의 자격이 상승한다.

2. 클라이언트의 송신 속도가 높을 수록 슈퍼 피어로서의 자격이 상승한다.

3. 클라이언트의 성능이 좋아서 초당 실행 프레임레이트(framerate)가 높은 경우 슈퍼 피어로서의 자격이 상승한다. 가령, 물리 시뮬레이션을 수퍼피어가 전담하는 게임 개발의 경우 이것이 중요해진다. 자세한 것은 Proud.CNetClient.SetApplicationHint 도움말을 참고하라.


직접 개발하게 된 네트워크 API의 경우엔 위의 조건을 계산해내는 기능을 직접 구현해야 하겠지만, 프라우드넷에서는 그와 관련된 함수가 이미 존재한다. Proud.CNetServer.GetMostSuitableSuperPeerInGroup() 함수와 Proud.CNetServer.GetSuitalbeSuperPeerRankListInGroup() 함수가 바로 그것이다.


이 함수들의 설명은 다음과 같다 :


virtual HostID Proud::CNetServer::GetMostSuitableSuperPeerInGroup(

HostID groupID,

const CSuperPeerSelectionPolicy & policy = CSuperPeerSelectionPolicy::GetOrdinary(),

const HostID * excludees = NULL,

intptr_t excludeesLength = 0

)


이 메서드는 groupID가 가리키는 P2P 그룹에 있는 멤버들 중 가장 최적의 슈퍼 피어를 찾아서 알려주는데, P2P 그룹을 생성하거나 변경한 직후에는 슈퍼 피어 후보자를 제대로 얻지 못할 수도 있다. 처음 이 메서드를 호출한 이후 2-5초 후에 다시 호출해주면 더 정확한 슈퍼 피어를 찾을 수 있다.


매개변수

groupID :: 슈퍼 피어를 찾고자하는 P2P 그룹의 ID

policy :: 슈퍼 피어를 선정하는 정책. 자세한 설명은 CSuperPeerSelectionPolicy 를 참고.

excludees :: groupID가 가리키는 P2P 그룹의 멤버 중 excludees에 들어있는 멤버들은 제외하고 선별한다. 예를 들어 이미 사용하던 슈퍼 피어가 자격을 박탈당한 경우 다시 재선발되는 것을 막고자할 때 유용하다.


반환값

슈퍼 피어로서 가장 적격인 클라이언트의 HostID. P2P 그룹이 찾지 못했거나 excludees에 의해 모든 멤버가 필터링되면 HostID_None을 리턴한다.


virtual int Proud::CNetServer::GetSuitableSuperPeerRankListInGroup(

HostID groupID,

SuperPeerRating * ratings,

int ratingsBufferCount,
const CSuperPeerSelectionPolicy & policy = CSuperPeerSelectionPolicy::GetOrdinary(),

CFastArray<HostID> & excludees = CFastArray<HostID>

)


이 메서드 역시 Proud.CNetServer.GetMostSuitableSuperPeerInGroup() 와 마찬가지로 최적의 슈퍼 피어 후보자를 찾아주지만 이전 메서드와 다른 점은 최고 순위의 후보자 뿐만 아니라 차순위의 후보자 역시 찾아서 준다.


매개변수

groupID :: 슈퍼 피어를 찾고자하는 P2P 그룹의 ID

ratings :: 여기에 적합한 슈퍼 피어 후보자 목록이 채워져서 반환된다. 가장 적합한 후보자 순으로 정렬되어 채워진다.

ratingsBufferCount :: rating의 배열 항목 갯수이다. 이 함수가 리턴하는 배열의 크기는 이 크기 이상은 채우지 않는다.

policy :: 이전 메서드의 설명과 같다.

excludees :: 이전 메서드의 설명과 같다.


반환값

ratings에 채워진 항목의 갯수를 반환한다. P2P 그룹을 찾지 못했거나 excludees에 의해 모든 멤버가 필터링되면 0을 반환한다.



위의 함수들을 적절하게 이용하면 적합한 클라이언트를 슈퍼 피어로 선정할 수 있을 것이고, 게이밍의 질을 향상시킬 수 있을 것이다.


참고

http://guide.nettention.com/cpp_ko#super_peer

http://help.nettention.com/cpp/1.7.31494-master/class_proud_1_1_c_net_client.ndn/

반응형

ProudNet 소개


일반적인 싱글 플레이 게임을 개발할 때는 상관없는 이야기이지만, 멀티 플레이 요소가 조금이라도 들어가거나 멀티 플레이 위주의 게임에서는 항상 고민해야할 것이 있다. 그것을 바로 네트워크를 어떻게 개발할 것인가? 라는 부분이다. 이에 대한 해답은 여러가지가 있을 수 있는데, 대표적인 두 가지 방법으로는 직접 구현하기와 다른 회사의 API를 사용하는 것이 있다.


하지만 직접 구현하는 방법의 경우에는 굉장한 시간과 노력이 들기 마련이다. 그리고 팀 내에 아직 네트워크 개발의 인력이나 노하우가 없다면 연구 개발하는데 걸리는 시간과 비용은 기하급수적으로 늘어나게 될 것이다. 이렇듯이 직접 개발하는 것이 어려운 상태라면 개발자의 눈은 자연스레 다른 회사의 API로 돌아갈 수 밖에 없다. 이미 시중에는 여러 종류의 네트워크 API들이 나와있다. 서버 구축 비용을 절감할 수 있는 클라우드 방식의 Photon Cloud라던가 구글에서 얼마전 서비스를 시작한 Firebase라던가 하는 것들이 있다. 하지만 이번에 소개할 것은 Nettention사의 ProudNet이라는 게임 네트워크 엔진이다.


공식 홈페이지 :: http://proudnet.com/



이 프라우드넷은 위의 이미지에서 보이듯이 이미 여러 게임에서 많이 사용된 게임 네트워크 API로서 캡콤의 스트리트 파이터 5나 넥슨의 마비노기 마영전 같은 대형 프로젝트에서도 사용되었고 그로 인해 게임 네트워크 엔진으로서 필요한 기능 역시 확실하게 갖추고 있다.


일반적인 게임 회사가 아닌 공부하려는 학생과 개인 개발자나 인디 게임팀 혹은 인디 게임 회사의 경우, 이런 API를 사용하는데 드는 비용이 무엇보다 중요할 것이다.



이에 대한 정책도 확실한데 공부를 위한 퍼스널 라이센스 같은 경우 영리목적 프로젝트에는 사용이 불가능하고 동시 접속자 수 역시 20명으로 제한되지만 API의 기능 자체는 제한없이 확실하게 사용해서 공부를 할 수 있고, 소규모 개발팀을 위한 인디 라이센스 역시 무료로 제공되고 있다.


그리고 또 하나의 장점은, 넷텐션은 국내 개발사로서 프라우드넷 공식 문서가 완벽한 한글로 지원된다는 점이다. 외국의 API나 게임 엔진을 사용할 때는 최신 기능에 대한 공식 문서 번역은 항상 한 발 느리기 때문에 구글링을 해서 찾아야하는 번거로움이 없기 때문에 개발 속도가 한층 더 빨라질 수 있다.


프라우드넷 공식 문서 :: http://guide.nettention.com/cpp_ko#intro

반응형

Unity3D로 게임 클라이언트를 제작하고 Nettention사의 ProudNet을 이용하여 게임 네트워크를 담당하여 게임을 제작하는 과정에서 안드로이드 빌드로 포팅해서 모바일에서 실행시켰을 때 클라이언트가 서버에 제대로 접속하지 못하는 문제점을 발견했다. 똑같은 클라이언트에 대해서 PC로 포팅 했을때에는 서버에 제대로 접속이 되는데에 반해서 모바일 버전은 접속이 안되는 바람에 하루 가까이 시간을 쏟아서 해결책을 찾았는데 문제의 해결법은 매우 간단했다.

 

해결책

  1. File 메뉴에서 Build Setting을 연다.(혹은 Ctrl + Shift + B 단축키로 열 수 있다.)
  2. Platform 리스트 중에서 Android를 선택하고 그 아래에 있는 Player Setting ... 버튼을 누른다.
  3. 그러면 Inspector 창에 Player Setting이 열리는데 아래쪽에 안드로이드 모양 탭중에서 Other Setting이라는 메뉴를 찾는다.
  4. 그 중에 굵은 글씨로 Configuration 메뉴 중에 Internet Access 라는 항목이 있다.
  5. 이 항목의 기본 값은 Auto로 되어 있는데 이것을 Require로 변경한다.

 


위의 그림처럼 Internet Access 항목을 Require로 변경해주면된다.

 

이상의 과정을 모두 끝내고 난 이후에 다시 안드로이드 버전으로 빌드하여 모바일에 설치하고 실행하면 서버 접속이 원활하게 이루어지는 것을 알 수 있다.

 

이러한 문제가 ProudNet API를 사용할 때만 나타나는 것인지 아니면 다른 네트워크 API를 사용했을때도 나타나는 문제인지는 모르겠지만 인터넷 상에서 이와 관련된 정보를 찾아보기가 쉽지 않아서 개인적으로 참고도 할 겸, 다른 사람이 찾아봐서 도움도 될 겸해서 포스팅을 남겨둔다.


반응형
  1. 2018.09.05 21:05

    비밀댓글입니다

    • wergia 2018.09.06 10:33 신고

      음 프라우드넷으로 네트워크 구현했을 때, 안드로이드가 서버에 접속못하는 문제는 internet access require가 제일 중요했었는데 그걸 설정했는데도 안된다는 말씀이시군요.

      혹시 Internet Access 설정의 5줄 위에 있는 Api Compatibility Level은 어떤 것으로 설정되어 있나요? 프라우드넷으로 작업하던 당시에 .NET 2.0 Subset이 아니면 안되었던 이유가 있었던 것 같은데 이 쪽도 연관이 있을 수 있다고 봅니다.

+ Recent posts