상세 컨텐츠

본문 제목

18장. 좀비 서바이벌(멀티플레이)

유니티/게임만들기

by MJ_119 2024. 4. 29. 20:45

본문

이 장에서 다루는 내용

 - 로컬과 리모트의 구분

 - 네트워크 권한에 따라 코드를 분리하는 방법 

 - 클라이언트 사이에서 수치가 동기화되는 흐름

 - 게임 서버의 종류

 - RPC

 - PUN(Photon Unity Network) 준비하기

 - 로비를 만들고 매치메이킹을 구현하는 방법

 

@ 네트워크 동기화

 : 4인 멀티플레이어 게임에서 플레이어 캐릭터는 총 16명이다.

 

 - 로컬 오브젝트 : 주도권이 자신에게 있음

 - 리모트 오브젝트 : 주도권이 네트워크 너머의 타인에게 있음

 

 

@ 로컬 권한 검사 

if(!local)
{
	return;
}

 

 

@ 게임 서버의 종류

 - 기본적으로 네트워크 기반 게임은 <서버-클라이언트> 방식으로 동작

 - 전용 서버(Dedicated Server) : 서버의 모든 자원이 온전히 네트워크 서비스를 유지하는데 사용하며, 서버가 플레이어로서 게임에 직접 참가하지 않는 형태 // 다른 방식에 비해 고정비용이 많이 발생함.

 

 - 리슨 서버(Listen Server) : 전용 서버가 없는 대신 플레이어 클라이언트중 하나가 서버 역할을 맡음. 'Play as Host' 라고 부르기도 함. 서버 역할을 맡은 클라이언트를 방장, 호스트, 마스터클라이언트 등으로 부름.

 전용 서버에 비해 서비스 유지비용이 적음. 물리적으로 가까운 위치에 있는 플레이어들끼리는 네트워크 반응속도가 전용 서버보다 빠를 수 있음. // 호스트의 컴퓨터 성능에 따라 네트워크 품질이 달라짐 // 호스트가 게임을 종료할 시 진행중인 게임을 일시정지하고 남은 클라이언트중에 새로운 호스트를 선정하는 절차(Host Migration)이 필요함.

 

 - P2P(Peer-to-Peer) : 게임에 참가한 클라이언트들 모두가 호스트 역할을 겸함. 네트워크룸을 유지하는데 필요한 연산 대부분을 독점하는 호스트 없이 클라이언트가 각자 자신의 월드에서 자신의 담당 연산을 실행하고 다른 클라이언트에 결과를 전파함. // 리슨 서버방식과 유사하지만 호스트가 게임도중에 접속을 종료하면 호스트를 교체하는 과정이 필요가 없음. 서버 유지비용 또한 발생하지 않으며 클라이언트끼리 직접 연결되기 때문에 클라이언트 수가 적은 경우에 한해서 다른 방법보다 네트워크 반응속도가 빠름. // 개발자 입장에서 P2P를 사용하면 프로그램의 처리 흐름을 직관적으로 설계할 수 있음.

 참가자가 증가할수록 반응 속도가 눈에 띄게 느려짐, 통상적으로 16명까지를 상한선으로 여김.

 

 @ 리슨 서버 방식을 사용하여 구현할것임.

 다만 매치메이킹 과정에서는 포톤Photon에서 제공하는 전용 클라우드 서버를 사용.

 

 

 

@ 네트워크 권한 분리

 : 네트워크 게임에서는 공정한 결과를 보장하고 수치에 대한 위변조를 방지해야 함.

 

 - '중요한 연산은 모두 서버(호스트)에 위임' 규칙을 가능하면 지켜야함

이유 : 동기화에 오차가 존재하는 경우 기준이 되는 월드를 정하기 위해, 클라이언트의 위변조를 막기 위해

 

 - 예를들어 FPS 게임을 한다고 치면 호스트 A에서 총을 쏘는 처리를 실행하고 결과를 BCD에 전달해주고, B, C, D 클라이언트에서는 비주얼 이펙트, 효과음재생, 애니메이션재생 등을 처리하는게 효율적임.

 

 

 @ RPC(Remote Procedure Call)

 - 호스트에 처리를 위임하고, 호스트가 처리결과를 클라이언트에 전파하려면 RPC를 구현해야 함. 

 - 어떤 메서드나 처리를 네트워크를 넘어 다른 클라이언트에서 실행하는 것.

 

 @ 포톤 ( PUN 2 )

 - 멀티 플레이어게임에 필요한 클라우드 서버 대여 서비스, 실시간으로 게임 서버를 관리할 수 있는 웹 서비스, 여러 게임 엔진에 플러그인 형태로 삽입할 수 있는 네트워크 엔진 등을 제공.

 

 

 @ 로비 만들기

using Photon.Pun; // 유니티용 포톤 컴포넌트들
using Photon.Realtime; // 포톤 서비스 관련 라이브러리
using UnityEngine;
using UnityEngine.UI;

// 마스터(매치 메이킹) 서버와 룸 접속을 담당
public class LobbyManager : MonoBehaviourPunCallbacks {
    private string gameVersion = "1"; // 게임 버전

    public Text connectionInfoText; // 네트워크 정보를 표시할 텍스트
    public Button joinButton; // 룸 접속 버튼

    // 게임 실행과 동시에 마스터 서버 접속 시도
    private void Start() {
        // 접속에필요한 정보(게임 버전) 설정
        PhotonNetwork.GameVersion = gameVersion;

        // 설정한 정보로 마스터 서버 접속 시도
        PhotonNetwork.ConnectUsingSettings();
        
        // 룸 접속 버튼 잠시 비활성화
        joinButton.interactable = false;

        // 접속 시도중임을 텍스트로 표시
        connectionInfoText.text = "마스터 서버에 접속중...";

    }

    // 마스터 서버 접속 성공시 자동 실행
    public override void OnConnectedToMaster() {

        // 룸 접속 버튼 활성화
        joinButton.interactable = true;

        // 접속 정보 표시
        connectionInfoText.text = "온라인 : 마스터 서버와 연결됨";

    }

    // 마스터 서버 접속 실패시 자동 실행
    public override void OnDisconnected(DisconnectCause cause) {

        // 룸 접속 버튼 비활성화
        joinButton.interactable = false;

        // 접속 정보 표시
        connectionInfoText.text = "오프라인 : 마스터 서버와 연결되지 않음 /n접속 재시도 중...";

        PhotonNetwork.ConnectUsingSettings();
    }

    // 룸 접속 시도
    public void Connect() {

        // 중복 접속 시도를 막기 위해 접속 버튼 잠시 비활성화
        joinButton.interactable = false;

        // 마스터 서버에 접속 중이라면
        if (PhotonNetwork.IsConnected)
        {
            // 룸 접속 실행
            connectionInfoText.text = "룸에 접속중...";
            PhotonNetwork.JoinRandomRoom();
        }
        else
        {
            // 마스터 서버에 접속 중이 아니라면 마스터 서버에 접속 시도
            connectionInfoText.text = "오프라인 : 마스터 서버와 연결되지 않음 /n접속 재시도 중...";

            // 마스터 서버에 재접속 시도
            PhotonNetwork.ConnectUsingSettings();
        }
    }

    // (빈 방이 없어)랜덤 룸 참가에 실패한 경우 자동 실행
    public override void OnJoinRandomFailed(short returnCode, string message) {

        // 접속 정보 표시
        connectionInfoText.text = "빈 방이 없음, 새로운 방 생성중...";

        // 최대 4명을 수용 가능한 빈 방 생성
        PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = 4 });

    }

    // 룸에 참가 완료된 경우 자동 실행
    public override void OnJoinedRoom() {

        // 접속 상태 표시
        connectionInfoText.text = "방 참가 성공";

        // 모든 룸 참가자가 Main 씬을 로드하게 함.
        PhotonNetwork.LoadLevel("Main");
    }
}

 

 - OnConnectedToMaster() 메서드 : 포톤 마스터 서버에 접속 성공한 경우 자동으로 실행 됨.

 접속에 성공했다는 메시지를 표시하고, 접속버튼인 Join Button이 상호작용 가능하도록 전환해야 함.

 

 - OnDisconnected() 메서드 : 마스터 서버 접속에 실패했거나, 이미 마스터 서버에 접속된 상태에서 어떠한 이유로 접속이 끊긴 경우 자동으로 실행 됨.

 - 접속 끊김의 원인에 대한 정보가 DisconnectCause 타입으로 메서드에 자동입력됨.

 - 이 메서드에서는 끊긴 사실을 표시하고 룸 접속 버튼을 비활성화 해야함, 그리고 마스터 서버로 재접속을 시도해야 함.

 

 - Connect() 메서드 : 씬의 Join Button을 클릭했을 때 실행할 메서드. 

 - 매치메이킹 서버(마스터 서버)를 통해 빈 무작위 룸에 접속을 시도함.

 

 - OnJoinRandomFailed() 메서드 : 랜덤 룸 접속에 실패한 경우 자동으로 실행. 마스터 서버와의 연결이 끊긴 것이 아님.

 예를들어 참가 가능한 랜덤 룸이 없는 경우 실행 => 완전히 새로운 룸을 만들어 그곳에 접속하는 처리를 구현해야 함.

 - 생성된 룸은 리슨 서버 방식으로 동작하며 룸을 생성한 클라이언트가 호스트 역할을 맡게 됨.

 

 - OnJoinedRoom() 메서드 : 룸 참가에 성공한 경우 자동 실행 됨. CreateRoom()을 사용해도 실행 됨.

본격적인 게임이 진행되는 Main 씬을 로드함.

 - SceneManager.LoadScene("Main") 대신 PhotonNetwork.LoadLevel("Main")을 사용한것에 주의!

SceneManager.LoadScene은 이전 씬의 모든 게임 오브젝트를 삭제하고 다음 씬을 로드하므로 로비 씬의 네트워크 정보가 유지되지 않음. // 또한 플레이어들이 서로 동기화 없이 각자 Main씬을 로드하기 떄문에 다른 사람의 캐릭터가 보이지 않음.

 

반면 PhotonNetwork.LoadLevel () 메서드는 어떤 씬을 로드하고, 해당 씬의 구성이 플레이어 사이에서 동기화되도록 유지함. 룸의 플레이어들이 '함께 새로운 무대로 이동'하는 메서드.

 뒤늦게 입장한 플레이어를 위한 별도의 작업을 구현할 필요가 없어 편리함.

 

 

 

 

 

관련글 더보기