이문회우

[Unity Netcode] 멀티 개념 잡기 / 넷코드 + 포톤 + 메이플스토리 월드 본문

[Unity Netcode] 멀티 개념 잡기 / 넷코드 + 포톤 + 메이플스토리 월드

Koesob 2023. 4. 18. 16:07

다음을 비롯해 여러 정보를 참고하여 글을 썼습니다. ㅎㅎ

About Netcode for GameObjects | Unity Multiplayer Networking (unity3d.com)

COMPLETE Unity Multiplayer Tutorial (Netcode for Game Objects) - CodeMonkey

[Unity] 게임 서버/네트워크 라이브러리 Mirror & Mirage 소개 | by JongHa Shin (John) | Ward Games | Medium


유니티와 관련한 멀티 플레이 패키지

 

Netcode for GameObject(이하 넷코드) : 유니티가 기존 UNet을 버리고 갈아탄 멀티 플레이용 패키지. 문서가 별로 없다.

Photon(이하 포톤) : 넷코드가 나오기까지의 기간 동안 점유율을 차지한 패키지.

Mirror : UNet을 오픈 소스로 계속 개발해나간 패키지.

Mirrage : 다시 Mirror를 기반으로, 서버 개발자들이 개발해나간 패키지.

 

메이플스토리 월드 : 유니티 패키지는 아니지만, 유니티 + 멀티 플레이 기반 플랫폼. 개념은 엇비슷


 

멀티 플레이의 기본은 두 컴퓨터를 연결하는 것. 그리고 동기화

 

1. P2P

플레이어가 서로 정보를 계속 주고 받는 식으로 연결과 동기화 상태를 유지한다.

게임을 진행하는 중에도 정보는 계속 주고 받기 때문에 컴퓨터 연산이 많다. (게임 처리 + 게임 정보 주고 + 받기)

따라서 모든 컴퓨터가 일정 사양은 돼야한다. 게임 자체의 연산이 많아질수록 기준선은 높아진다.

참고로, P2P가 Player to Player가 아니다. Peer to Peer임.

애초에 이 개념이 게임에서 나온 개념이 아니라 그렇다. 가장 흔하게 볼 수 있는 표현으로는 P2P 사이트. 토렌트 등

 

2. 리슨 서버

모든 컴퓨터가 사양이 좋을 필요가 있을까?

한 컴퓨터가 게임 관련 처리 다 하고 전송하면, 나머지는 받기만 하면 연산이 확 줄지 않을까?

라는 생각의 결과라고 보면 된다.

※ 게임의 처리를 한 쪽에서 다 해서 전송하는 것이지 나머지 쪽에서도 입력된 값 등은 전송하긴 한다.

이 때, 게임 관련 처리를 다 하는 컴퓨터를 '호스트'라고 한다. 게임판을 연 주인장이다.

그 게임판에 참가하는 사람들을 '클라이언트'라고 한다. 손님인것.

물론, 이 도박장게임장에 대한 처리를 주인장(호스트)이 다 하고 있기 때문에 주인장 쪽에서 혹시 모를 검은 손을 쓸 수 있다. 한 편, 정작 호스트의 컴퓨터가 안 좋으면 클라이언트의 컴퓨터 사양과 무관하게 게임이 버벅일 수 있다.

 

3. 전용 서버

존멋

2번 리슨 서버가 특정 플레이어가 '게임에 참여하는 플레이어'와 '게임을 진행하는 심판'을 동시에 맡는 것이라고 하면, 전용 서버는 이 '플레이어'와 '심판'의 역할을 분리한 것이라 생각하면 된다.

전용 서버 쪽은 심판의 역할을 하고 처리의 결과를 보내준다. 그리고 나머지 플레이어들은, 적어도 심판이 속이지 않는다면 안심하고 플레이를 즐기면 된다.

이때 전용 서버 쪽에서 큰 연산이 필요한 부분은 맡아서 처리할 수 있는데, 그렇게 되면 플레이어들은 보다 낮은 사양으로도 게임을 즐길 수 있게 된다.

물론, 그럴 경우, 전용 서버 쪽에서는 훠어어얼씬 높은 사양을 요구한다는 의미이기에 게임을 서비스하는 쪽에서 부담이 커질 수 있다.


 

읽어보면 알 수 있겠지만, 게임 쪽에서 서버는 사실 '플레이에 참여하고 있지 않은 또 다른 클라이언트'라고도 볼 수 있다.

 

포톤의 코드가 이를 극명히 드러내는데, 포톤은 다른 곳에선 'Server'라고 할 법한 코드를 'MasterClient'라는 표현을 쓴다.

달리 말하면, 포톤은 '전용 서버'를 염두에 두기엔 방향성 면에서 안 맞을 수도?

 

이번에 다룰 다른 두 개, 넷코드와 메이플스토리 월드는 반대로 'Client'와 'Server'를 명확히 구분한다.

 

그런데 이 Client와 Server의 구분. 더 자세히 말해서 '역할 구분'은 멀티 게임에서 핵심이다.

결국, 네트워크를 통해 정보를 주고 받는 작업 + 그렇게 몰려온 정보들을 처리하는 작업은 비용이 굉장해 엄청나! 한 작업이기 때문에

보내지 않고 클라이언트 쪽에서 처리할 것과, 꼭 서버에서 처리가 필요한 코드를 구분하는 것은 매우 중요하다.


멀티 플레이 패키지 구조

1. 각 컴퓨터를 연결할 방법

2-1. 멀티 플레이 상황에서 각각의 플레이어를 식별할 방법

2-2. 데이터를 공유할 방법

3. Client-Server 역할 구분할 방법


흐름상 2번부터 설명하자면,

 

2-1. 쉬운 방법이 고유 ID를 붙이는 것이다.

NetCode : NetworkManager(플레이어 식별용) - NetworkObject(오브젝트 식별용) 스크립트를 붙인다.

네트워크 매니저를 통해서 네트워크 오브젝트들을 관측하고 있다는 느낌이다.

'Network'라는 직관적인 표현을 쓸 수 있는 퍼스트 파티의 위엄. 그런 면에서 Mirror도 과거 UNet을 기반으로 하기에 Network라는 직관적인 표현이 들어간 코드가 많다.

 

Photon : MonoBehaviourPunCallbacks 상속 받기(플레이어 식별용) - PhotonView(오브젝트 식별용) 스크립트를 붙인다.

마찬가지로, PhotonView 붙은 애들을 관측하는 느낌

 

만일 공유할 필요 없는 오브젝트라면 안 붙이면 그만.

 

참고로 메이플스토리 월드는 자동으로 붙여준다. 애초에 얘는 멀티 플레이가 기본 전제라 그렇다.

 

2-2. 데이터를 공유할 방법

데이터를 전송하는 것은 꽤 빡센 작업이다. 내 컴퓨터에선 이렇게 읽고 쓰지만, 상대의 컴퓨터에선 다르게 읽고 쓸 수도 있거든.

그래서 아무 데이터나 보낼 수는 없으며, Serialize. 그러니까 직렬화를 꼭 해줘야 한다.

각각의 패키지는 사용자가 만든 구조체 등을 으뜨케든 보낼 수 있도록 하는 기능을 제공하는데, 이 경우에도 Serialize가 가능하도록 바꾸는 것이다.

 

Netcode : INetworkSerializable로 어떤 식으로 Serialize를 할 것인지 정해주면 사용자가 만든 구조체도 공유를 할 수 있다.

Photon : IPunObservable 마찬가지. 다시 말하지만, 구조 자체는 다 비슷하더라고.

 

메이플스토리 월드는 애초에 변수를 만들 때 둘 중 하나를 선택하도록 되어 있다.

와중에 사용자가 만든 구조체는 공유가 안됨. 코드를 추가로 작성해서 보내기 전에 공유 가능한 변수로 분해하고, 받는 쪽에서 분해돼서 온 변수들을 합쳐서 구조체로 재조합하도록 해야한다.

 

3. Client-Server 역할 구분

<Photon>

포톤의 경우, MonoBehaviourPunCallbacks를 상속 받으면 photonView 변수를 사용할 수 있다.

그리고 이 photonView.RPC()라는 함수를 통해서 '너는 다른 모든 클라이언트에서 실행될거야.' '너는 마스터 클라이언트에서만 실행될거야'라고 지정하는 식으로 역할 구분이 이뤄진다.

사실, 앞서 말했듯 Client-Server 역할 구분이 명확하진 않다. 약간 리슨 서버를 전제로 개발하는 느낌

 

<Netcode>

넷코드의 경우, 함수 앞에 [ServerRPC]나 [ClientRPC]를 달아두면 다음에 그 함수를 호출할 때, 전자는 클라이언트에서 호출되더라도 서버로, 후자는 서버에서 실행되더라도 Client에서 실행 처리가 된다.

이런 흐름이다.
서버 to Clinet
단, 함수 뒤에 ClientRpc 또는 ServerRpc 표현을 꼭 붙여야한다.

메이플스토리 월드에서도 똑같이 ServerRpc, ClientRpc를 적용한다.

와중에 유니티를 기반으로 해서 그런가 구현이나 방식이 정말 엇비슷하다.


1. 각 플레이어를 연결할 방법

이 파트는 네트워크를 공부해야하기 때문에 내용이 딥-하다.

그리고 사실 그 공부를 하다보면 결국 게임을 완성할 수 없기 때문인지 이 계층은 대부분의 패키지에서 확실히 구분되어 있다.

즉, 게임 개발할 때는 2번 3번 신경 쓰면서 만들어내고, 연결 방법은 각 패키지들이 거의 전담을 하고 있다.

그래서 각 패키지에 적혀있는 함수를 호출하기만 하면 연결이 완료되고 로비가 생긴다.

 

연결을 하는 형태에는 세 가지 정도가 있다.

1. IP로 직접 연결 : 보안상 이슈가 있어 방화벽부터 어떻게 해야한다.

→ Netcode나 Photon을 사용하면 가능은한데 다시 말하지만 방화벽부터 뚫어야 한다. 더군다나 일반 플레이어에게 자기 IP를 알라고 하는 것은 꽤 빡센 일

2. 키 값을 통해 연결한다.

→ Photon은 기본적으로 제공한다. Netcode로는 구현이 안되고, Unity Gaming Service의 Relay라는 기능을 함께 써야한다.

3. 로비

→ 마찬가지로 Photon은 기본적으로 제공, Unity Gameing Service의 Lobby 기능 함께 써야함.

 

Netcode 쪽은 각각의 역할별로 코드 또한 나눠 놓은 느낌이라 생각하면 된다.

 

그리고 메이플스토리 월드는 애초에 만나서 시작한다. 그 위에 건물을 지어 올리라는 느낌.

약간

포톤이나 넷코드 : 게임 만드시면, 저희가 연결해드릴게요.

이런 느낌이라면

메이플스토리 월드 : 일단 연결부터 하시고, 게임 만드세요.

이런 느낌

 

다시 말하지만, 이쪽 파트는 프로그래머가 손 댈 수 있는 부분이 거의 없다.

그저 요청만 하고, 요청 결과를 받을 뿐.


이해에는 이전에 경험한 '메이플스토리 월드'가 도움이 됐다. 그래서 메이플스토리 언급이 많다.

진심으로 유니티로 멀티 플레이 게임을 만들고 싶다면

1. 메이플스토리 월드로 게임 하나 만들어보기

2. 멀티 플레이 패키지 선택해서 공부시작

3. 유니티로 개발 시작

이 순서가 맞지 않나 싶을 정도로 개념 이해에 도움이 많이 됐다.

그저 루아라든지 공부할게 더 많아지는 이슈가 있지만, 그것조차 C#의 최신 문법을 이해하는데에 도움이 되지 않을까 싶다.

'' 카테고리의 다른 글

[Unreal Behavior Tree] 공식 문서 읽기  (2) 2023.05.14
게임 프로그래밍 패턴  (0) 2023.04.20
파란색 / 스즈메의 문단속  (0) 2023.03.31
수면 위에 풍경 있어요!  (0) 2023.03.21
파운데이션 시리즈 1~3  (3) 2023.01.23