2022.09.13 경일 메타버스 24주차 1일 특강 수업내용. 포톤 네트워크 엔진
포톤에서 제공하는 유니티 네트워크 솔루션
포톤과 관련된 여러 가지 기능을 제공
API
ConnectUsingSettings() : 매치메이킹 서버에 접속
JoinRandomRoom() : 현재 접속 가능한 방 중 임의로 접속한다.
LoadLevel() : 방에 있는 모든 클라이언트를 특정 씬으로 이동시킨다.
CreateRoom() : 방을 생성한다.
방에 있는 여러 게임 오브젝트 중 로컬과 리모트를 구분하고, 데이터 동기화 기능을 제공
데이터 동기화는 IPunObservable.OnPhotonSerializeView()에서 제공
IsMine : 프로퍼티로 로컬과 리모트 구분 가능
기본 제공 컴포넌트
PhotonTransformView : 트랜스폼 동기화 제공
PhotonAnimatorView : 애니메이터 동기화 제공
PhotonRigidbodyView : 리지드바디 동기화 제공
photonView 제공
PhotonView 컴포넌트가 추가되어 있어야 함
핵심
”4인 멀티플레이어 게임에서 플레이어 캐릭터는 총 16명이다.”
로컬 오브젝트 : 주도권이 자신에게 있음
리모트 오브젝트 : 주도권이 네트워크 너머의 타인에게 있음
로컬 권한 검사
로컬이든 리모트든 게임 오브젝트의 컴포넌트 구성은 같다
⇒ 모두 사용자 입력을 받을 수 있다.
플레이어 조작 입력은 로컬 오브젝트에만 반영되어야 한다.
이를 구현하기 위해서는 오브젝트가 로컬 권한을 가지고 있는지 검사해야 한다.
서버(Server)는 클라이언트들이 참가할 수 있는 네트워크 공간을 마련하는 컴퓨터
호스트(Host)라고도 불린다.
게임 속 상호작용 연산도 담당
클라이언트(Client)는 서버에 참가하여 게임을 플레이하는 컴퓨터
네트워크 게임의 서버-클라이언트 방식
전용 서버 (Dedicated Server)
리슨 서버 (Listen Server)
P2P (Peer-to-Peer)
서버의 모든 자원이 온전히 네트워크 서비스를 유지하는 데 사용되며, 서버가 플레이어로서 게임에 직접 참가하지 않는 형태
장점
언제든지 참가 가능
고정된 고성능 서버 제공 → 쾌적한 환경
단점
클라이언트 중 하나가 서버 역할
‘Play as Host’라고도 불린다.
마인크래프트, 로블록스 등
장점
적은 서비스 유지 비용
물리적으로 매우 가까운 플레이어 사이에서는 전용 서버보다 빠른 네트워크 반응 속도
단점
호스트 플레이어의 컴퓨터 성능에 따라 달라지는 네트워크 품질
호스트가 게임을 종료할 경우, 남은 클라이언트 중 새로운 호스트를 선정하는 절차 (Host Migration) 필요
게임에 참가한 클라이언트 모두가 호스트
서로 직접 연결된 형태
P2P의 연산
네트워크 게임에서는 일반적으로 호스트는 클라이언트에 비해 연산 부담이 크다
→ 네트워크 룸을 유지하는 데 필요한 연산 + 게임 속 중요한 연산
P2P에서는 연산을 독점하는 호스트가 없다
→ 각 클라이언트가 자신의 담당 연산을 실행, 결과 전파
장점
호스트 교체 과정 (Host Migration) 불필요
서버 유지비용 없음
클라이언트 수가 적은 경우 다른 방법보다 빠른 네트워크 반응 속도
프로그램 처리 흐름을 직관적으로 설계 가능
단점
참가자가 증가할수록 느려지는 반응 속도
다른 모든 클라이언트와 직접 연결 필요
연결 수가 늘어날수록 빠르게 늘어나는 부담 연산량
수치 변조에 취약
호스트가 있으면 중요 연산과 데이터는 호스트가 관리
P2P는 각 클라이언트가 연산 → 위조된 수치의 전파 위험이 큼
리슨 서버나 P2P 방식을 사용해도 참가할 클라이언트들이 서로를 찾아 방 하나에 모이는 과정이 필요 ⇒ 전용 서버
이 전용 서버를 매치메이킹 서버라 부른다.
서버는 아니지만, 여러 클라이언트가 모인 네트워크 상의 가상 공간 단위
유니티의 씬이 아니다.
서로 다른 씬을 로드하는 것도 가능
하나의 룸에서 정보 공유 / 같은 장소(씬)으로 이동
비유 : 같은 주파수를 공유
네트워크 게임의 목표
공정한 결과 보장
수치에 대한 위변조 방지
서버와 클라이언트 사이의 권한을 분리
⇒ 규칙 “중요한 연산은 모두 서버(호스트)에 위임”
이유
동기화에 오차가 존재하는 경우 기준 월드를 선정
클라이언트의 위변조 행위 방지
중요한 연산은 호스트가 처리하고 클라이언트는 시각적 처리만 수행
→ 비주얼 이펙트, 효과음 재생, 애니메이션 재생 등
→ 반응 속도를 높이고 네트워크 대역폭 사용을 줄인다.
원격 프로시저 호출
메소드, 처리를 네트워크를 넘어 다른 클라이언트에서 실행
특정 클라이언트가 호스트에 처리를 위임 → 호스트는 그 결과를 모든 클라이언트에 전파
유니티 네트워크 솔루션 중 가장 대중적
포톤 PUN
Mirror
유니티 공식 라이브러리 - UNet
Deprecated (사용 되지 않음) 선언
현재 MLAPI 개발 중 → 실험 버전
인게임 서버
게임 자체 플레이에 대한 서버
뒤끝 등
아웃게임 서버
게임 외적인 부분, 즉 로그인 화면이나 캐시 샵 등을 처리하는 서버
ProudNet 등
PUN : Photon Unity Network, 유니티용으로 제작된 포톤 네트워크 엔진.
플랫폼과 상관없이 동작
포톤의 여러 API를 유니티 컴포넌트로 랩핑하여 제공
Multiplayer Game Development Made Easy | Photon Engine
PUN 2 - FREE | Network | Unity Asset Store
에셋 스토어 PUN 2 - FREE
로비 구현 예시 코드
using Photon.Pun; // 유니티용 포톤 컴포넌트들
using Photon.Realtime; // 포톤 서비스 관련 라이브러리
using UnityEngine;
using UnityEngine.UI;
// 마스터(매치 메이킹) 서버와 룸 접속을 담당
public class LobbyManager : MonoBehaviourPunCallbacks {
private static readonly string GAME_VERSION = "1"; // 게임 버전
public Text ConnectionInfoText; // 네트워크 정보를 표시할 텍스트
public Button JoinButton; // 룸 접속 버튼
// 게임 실행과 동시에 마스터 서버 접속 시도
private void Start()
{
// 접속에 필요한 정보를 설정한다.
PhotonNetwork.GameVersion = GAME_VERSION;
// 마스터 서버로 접속을 시도한다.
PhotonNetwork.ConnectUsingSettings();
// UI 표시
JoinButton.interactable = false;
ConnectionInfoText.text = "마스터 서버에 접속 중...";
}
// 마스터 서버 접속 성공시 자동 실행
public override void OnConnectedToMaster()
{
// UI 표시
JoinButton.interactable = true;
ConnectionInfoText.text = "온라인 : 마스터 서버와 접속 됨.";
}
// 마스터 서버 접속 실패시 자동 실행
public override void OnDisconnected(DisconnectCause cause)
{
// UI 표시
JoinButton.interactable = false;
ConnectionInfoText.text = "오프라인 : 재접속 중...";
// 재접속 시도
PhotonNetwork.ConnectUsingSettings();
}
// 룸(세션) 접속 시도
public void Connect()
{
// 접속 버튼을 비활성화
JoinButton.interactable = false;
// 서버에 접속 중이냐
if (PhotonNetwork.IsConnected)
{
// 접속 중이라는 것을 UI에 표시
ConnectionInfoText.text = "룸에 접속합니다!";
// 접속을 실행
PhotonNetwork.JoinRandomRoom();
}
// 아니라면
else
{
// UI 표시
ConnectionInfoText.text = "오프라인 : 재접속을 시도해주세요.";
// 다시 마스터 서버에 재접속 시도
PhotonNetwork.ConnectUsingSettings();
}
}
private static readonly RoomOptions ROOM_OPTIONS = new RoomOptions()
{
MaxPlayers = 4
};
// (빈 방이 없어)랜덤 룸 참가에 실패한 경우 자동 실행
public override void OnJoinRandomFailed(short returnCode, string message)
{
// UI 표시
ConnectionInfoText.text = "방 생성...";
// 방 생성
PhotonNetwork.CreateRoom(null, ROOM_OPTIONS);
}
// 룸에 참가 완료된 경우 자동 실행
public override void OnJoinedRoom()
{
// UI 표시
ConnectionInfoText.text = "방에 참가합니다.";
// 모든 클라이언트 Main 씬 로드
PhotonNetwork.LoadLevel("Main");
}
}
서버에 접속 시도
PhotonNetwork.ConnectUsingSettings()
DisconnectCause
서버 접속 여부를 나타내는 프로퍼티
PhotonNetwork.IsConnected
방을 선택하여 접속하고 싶다면 마스터 서버로부터 룸의 정보 / 갯수를 받아 UI로 목록(List)을 제공, 선택할 수 있도록 구성해야 한다.
룸의 정보(방 읽어들이기) : GetCustomRoomList()
임의의 접속 가능한 방에 접속 : JoinRandomRoom()
특정 방에 접속(선택하여 접속) : JoinRoom()
PhotonNetwork.CreateRoom()
이름 - null
을 넣으면 서버에서 랜덤 생성
룸 옵션 - 여러 옵션 - MaxPlayers
는 0을 넣으면 No Limit을 의미한다.
로비 타입, 티어 시스템 등등 추가 가능
씬을 로드할 때, SceneManager
가 아닌 PhotonNetwork.LoadLevel()
사용
현재는 포톤 서버를 사용하고 있지만, 후에 서버를 준비하고 도메인을 준비하고 어떤 포트를 사용할지 정하면 모두 PhotonServerSettings
라는 스크립터블 오브젝트에 업데이트해야 한다.
핵심 : 요청과 실행을 분리한다.
Player에 새롭게 추가된 컴포넌트
Photon View
네트워크를 통해 동기화될 모든 게임 오브젝트는 이 컴포넌트를 가져야 한다.
네트워크 상에서 구별 가능한 식별자 View ID를 부여한다.
변화한 수치를 관측하고 다른 클라이언트에게 전달 / 동기화할 수 있다.
관측할 컴포넌트를 Observed Components
리스트에 할당한다.
단, IPunObservable
인터페이스를 상속한 컴포넌트만 관측할 수 있다.
inspector 창
Synchronization
Off
Reliable Delta Compressed
상대방이 최근에 수신한 값과 동일한 값은 송신하지 않는다.
Reliable : 신뢰할 수 있다, 유실이 없다.
Unreliable
Unreliable On Change
Photon Transform View
플레이어의 Transform
을 동기화한다.
트랜스폼 컴포넌트 값의 변화를 측정하고, Photon View 컴포넌트를 사용해 동기화한다.
자신이 로컬이라면 트랜스폼의 속성값을 감지하고 리모트로 전송한다.
자신이 리모트라면 송신된 로컬의 값을 받아 자신의 트랜스폼에 적용한다.
로컬 게임 오브젝트와 리모트 게임 오브젝트 사이 트랜스폼 컴포넌트의 속성값을 동기화
Photon Animator View
플레이어의 애니메이션을 동기화한다.
애니메이터 컴포넌트의 파라미터를 동기화하여 서로 같은 애니메이션을 재생
자신이 로컬이라면 애니메이터 컴포넌트의 파라미터를 관측하고 리모트로 전달한다.
자신이 리모트라면 송신된 로컬의 값을 받아 자신의 애니메이터 컴포넌트의 파라미터에 덮어쓴다.
Camera Setup