This document is about: PUN 2
SWITCH TO

PUN Classic (v1), PUN 2, Bolt는 휴업 모드입니다. Unity2022에 대해서는 PUN 2에서 서포트하지만, 신기능의 추가는 없습니다. 현재 이용중인 고객님의 PUN 및 Bolt 프로젝트는 중단되지 않고, 퍼포먼스나 성능이 떨어지는 일도 없습니다. 앞으로의 새로운 프로젝트에는 Photon Fusion 또는 Quantum을 사용해 주십시오.

1 - 로비

서버 접속, 룸 접근과 생성

우선 이 튜토리얼의 핵심인 Photon 클라우드 서버에 connect하는 것과 에 참가 또는 필요시 생성에 대한 것을 알아 보겠습니다.

  1. 새로운 씬을 생성하여 Launcher.unity로 저장합니다.

  2. 새로운 C# 스크립트인Launcher를 생성합니다.

  3. Hierarchy 에서 빈 GameObject를 생성하고 Launcher로 이름을 부여해줍니다.

  4. GameObject LauncherLauncher C# 스크립트를 붙여줍니다.

  5. 아래와 같이 C# 스크립트 Launcher를 편집합니다:

    Coding Tip: Instead of copy/paste the code, you should type everything on your own, because you will likely remember it better. Writing comments is very easy, type /// on the line above a method or a property and you'll have the script editor automatically create a structured comment with for example the <summary> tag.
    코딩 팁: 코드를 복사/붙이기하는 대신, 스스로 타이핑을 하면 기억을 하기에는 더 좋습니다. 주석을 작성하는 것은 매우 쉬우며 메소드 또는 프로퍼티 윗줄에 ///를 입력하면 <summary> 태그의 형태로 구조적인 주석을 에디터가 자동적으로 생성해주게됩니다.

    C#

    using UnityEngine;
    using Photon.Pun;
    
    namespace Com.MyCompany.MyGame
    {
        public class Launcher : MonoBehaviour
        {
            #region Private Serializable Fields
    
            #endregion
    
            #region Private Fields
    
            /// <summary>
            /// This client's version number. Users are separated from each other by gameVersion (which allows you to make breaking changes).
            /// </summary>
            string gameVersion = "1";
    
            #endregion
    
            #region MonoBehaviour CallBacks
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during early initialization phase.
            /// </summary>
            void Awake()
            {
                // #Critical
                // this makes sure we can use PhotonNetwork.LoadLevel() on the master client and all clients in the same room sync their level automatically
                PhotonNetwork.AutomaticallySyncScene = true;
            }
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during initialization phase.
            /// </summary>
            void Start()
            {
                Connect();
            }
    
            #endregion
    
    
            #region Public Methods
    
            /// <summary>
            /// Start the connection process. 
            /// - If already connected, we attempt joining a random room
            /// - if not yet connected, Connect this application instance to Photon Cloud Network
            /// </summary>
            public void Connect()
            {
                // we check if we are connected or not, we join if we are , else we initiate the connection to the server.
                if (PhotonNetwork.IsConnected)
                {
                    // #Critical we need at this point to attempt joining a Random Room. If it fails, we'll get notified in OnJoinRandomFailed() and we'll create one.
                    PhotonNetwork.JoinRandomRoom();
                }
                else
                {
                    // #Critical, we must first and foremost connect to Photon Online Server.
                    PhotonNetwork.GameVersion = gameVersion;
                    PhotonNetwork.ConnectUsingSettings();
                }
            }
    
        #endregion
    
        }
    }
    
  6. C# 스크립트 Launcher를 저장합니다.

이 스크립트에 대해서 살펴보겠습니다. 우선 유니티 관점에서 살펴보고 이후에 PUN에 관련된 호출들에 대해서 살펴 보도록 하겠습니다.

  • Namespace:
    필수는 아니나 스크립트에 적당한 네임스페이스를 부여하는 것이 다른 에셋과 개발자의 코드와의 충돌을 막을 수 있습니다. 만약 다른 개발자가 Launcher 클래스를 생성하면 어떻게 될까요? 유니티가 이것을 거부 할 것이고 내가 바꾸거나 다른 개발자가 클래스의 이름을 변경 하여 유니티가 프로젝트를 실행할 수 있도록 해주어야 합니다. 에셋 스토어에서 다운로드 받은 에셋과 충돌 하게 된다면 매우 혼란 스러운 상황일 수 있습니다. 이제 Launcher 클래스는 내부적으로 Com.MyCompany.MyGame.Launcher 이며 이러한 네임스페이스를 갖고 있는 것은 거의 발생 하지 않을 것입니다. 왜냐하면 나만의 도메인을 사용하여 도메인을 역으로 네임스페이스를 만들기 때문에 안전하고 잘 구조화 된 형태이기 때문입니다. Com.MyCompany.MyGame 은 나의 도메인과 게임명으로 교체 되는데 convention 을 참고 하여 따라해 주시기 바랍니다.

  • MonoBehaviour 클래스:
    우리는 클래스를 MonoBehaviour 클래스에서 상속 받고 있으며 GameObject 또는 Prefab을 드롭할 수 있는 Unity Component로 전환하는 클래스가 됩니다. MonoBehaviour 클래스를 상속받은 클래스는 중요한 메소드와 프로퍼티들에 접근 할 수 있습니다. 이 경우에 우리는 두개의 콜백인 Awake() 그리고 Start() 를 사용할 것 입니다.

  • PhotonNetwork.GameVersion:
    gameVersion 변수는 게임버전을 나타내주는 변수입니다. 이미 출시되어 프로젝트에서 큰 변경사항이 있을 때까지는 "1" 로 남겨두십시오.

  • PhotonNetwork.ConnectUsingSettings():
    Start() 에서 우리는 공개 함수인 Connect()를 호출합니다. 여기에서 기억해두어야할 중요한 점은 이 메소드가 Photon Cloud에 연결 되는 시작 지점인 것 입니다.

  • PhotonNetwork.AutomaticallySyncScene:
    우리 게임은 플레이어 수에 따라 크기가 변경되는 경기장을 갖게 될 것이고 로드된 씬은 연결하고 있는 모든 플레이어에서 동일 한 것입니다. 우리는 Photon이 제공하는 매우 편리한 기능을 이용할 것 입니다:PhotonNetwork.AutomaticallySyncScene 이 값이 true일 때 MasterClientPhotonNetwork.LoadLevel()을 호출 할 수 있고 모든 연결된 플레이어들은 동일한 레벨을 자동적으로 로드 할 것입니다.

이 시점에서 Launch 씬을 저장할 수 있고 PhotonServerSettings (유니티 메뉴 Window/Photon Unity Networking/Highlight Photon Server Settings에서 선택)을 오픈하여 PUN 로깅을 "Full" 로 설정하도록 합니다:

launcher script inspector
PhotonSettings 디버그 레벨 설정

그리고나서 플레이를 누를 수 있습니다. 유니티 콘솔에서 로그를 볼 수 있을 것 입니다. 이중 한 가지 주목할 것은 "Received your UserID from server" 입니다. 이 로그는 성공적으로 연결이 진행되었다는 아주 좋은 신호입니다. 그리고 바로 다른 가능한 로그들을 볼 수 있을 것 입니다.

코딩할 때 가져야할 좋은 습관은 잠재적인 실패에 대하여 항상 테스트 하는 것 입니다. 여기에서는 컴퓨터가 인터넷에 연결되어 있다는 가정으로 했으나, 컴퓨터가 인터넷에 연결되어 있지 않았다면 어떻게 될까요? 한 번 알아보겠습니다. 컴퓨터의 인터넷 연결을 끄고 씬을 플레이합니다. 유니티 콘솔에서 다음의 에러를 볼 수 있을 것 입니다:

Connect() to 'ns.exitgames.com' failed: System.Net.Sockets.SocketException: No such host is known.

이상적으로는 스크립트가 이 문제에 대해서 알고 있어야 하고 이러한 상황에 올바르게 대처해야 하며, 어떤 상황 또는 문제가 발생 했는지에 상관없이 사용자에게 알려줘 적절한 대응을 할 수 있도록 해야 합니다.

이런 경우들을 처리하여 Launcher 스크립트내에서 파악하여 Photon Cloud에 연결되어 있는지 연결에 실패했는지 알 수 있습니다. 이 사항은 PUN 콜백의 아주 좋은 사례입니다.

PUN 콜백

PUN의 콜백은 매우 유연하고 2가지의 다른 구현 방식을 제공하고 있습니다. 모든 접근법에 대해 다룰 것이며, 상황에 따라 가장 적절한 것을 선택하여 사용할 것 입니다.

콜백 인터페이스 구현하기

PUN은 클래스내에서 구현할 수 있는 C# 인터페이스를 제공합니다:

  • IConnectionCallbacks: 연결 관련 콜백.
  • IInRoomCallbacks: 룸안에서 발생한 것에 대한 콜백.
  • ILobbyCallbacks: 로비 관련 콜백.
  • IMatchmakingCallbacks: 매치메이킹 관련 콜백.
  • IOnEventCallback: 수신된 이벤트에 대한 단일 콜백. C# 이벤트 LoadBalancingClient.OnEventReceived 와 '동등' 합니다.
  • IWebRpcCallback: WebRPC 오퍼레이션 응답 수신에 대한 단일 콜백.
  • IPunInstantiateMagicCallback: PUN 프리팹의 인스턴스화에 대한 단일 콜백.
  • IPunObservable: PhotonView 직렬화 콜백.
  • IPunOwnershipCallbacks: PUN 소유권 이전 콜백.

콜백 인터페이스는 등록되고 등록해제되어야 합니다. PhotonNetwork.AddCallbackTarget(this)PhotonNetwork.RemoveCallbackTarget(this)를 호출하십시오 (각각 OnEnable()OnDisable() 내에서와 같이)

이것은 클래스가 모든 인터페이스를 따르고 있는지 확인하는 안정한 방식이지만 개발자가 모든 인터페이스 선언에 대해서 구현해야 한다는 것 입니다. 대부분의 좋은 IDE에서는 이 작업을 매우 쉽게할 수 있도록 지원해주고 있습니다. 이 스크립트는 매우 많은 작업을 하는 수 많은 메소드를 가지게 될 것이지만, 모든 메소드들이 유니티 컴파일러가 알 수 있도록 반드시 구현해주어야 합니다. 대부분 또는 모든 PUN 기능을 사용할 때 스크립트가 정말로 무거워지게 됩니다.

우리는 앞으로 데이터 직렬화 튜토리얼을 위해 IPunObservable 을 사용할 것입니다.

MonoBehaviourPunCallbacks 확장하기

다른 기술로써 가장 많이 사용하는 마지막 기술이며 가장 손쉬운 방식입니다. MonoBehaviour 에서 상속 받은 클래스를 생성하는 대신에 특정 프로퍼티와 virtual methods를 사용할 수 있고 편의에 따라 오버라이드 를 제공하는 MonoBehaviourPunCallbacks 에서 상속 받을 것 입니다. 오타에 대해서 걱정할 필요도 없으며 모든 메소드를 구현할 필요가 없기 때문에 매우 유용합니다.

노트: 오버라이딩 할 때 대부분의 에디터는 기본 호출에 대한 기본 구현 코드를 자동적으로 채워 주지만 우리 경우에 있어서는MonoBehaviourPunCallbacks 의 일반 규칙으로 기본 메소드인 OnEnable() 또는 OnDisable() 호출할 필요가 없습니다. OnEnable()OnDisable()를 오버라이드했다면 항상 기본 클래스의 메소드를 호출하십시오.

PUN 콜백인 OnConnectedToMaster()OnDisconnected()으로 연습해 보겠습니다.

  1. C# 스크립트 Launcher를 편집합니다.

  2. 기본 클래스를 MonoBehaviour 에서 MonoBehaviourPunCallbacks으로 변경합니다.

    C#

    public class Launcher : MonoBehaviourPunCallbacks 
    {
    
  3. 파일 상단의 클래스 정의앞에 using Photon.Realtime; 을 추가합니다.

  4. MonoBehaviourPunCallbacks Callbacks 영역내에 다음 두 개의 메소드를 클래스의 끝에 추가합니다.

    C#

    #region MonoBehaviourPunCallbacks Callbacks
    
    public override void OnConnectedToMaster()
    {
        Debug.Log("PUN Basics Tutorial/Launcher: OnConnectedToMaster() was called by PUN");
    }
    
    public override void OnDisconnected(DisconnectCause cause)
    {
        Debug.LogWarningFormat("PUN Basics Tutorial/Launcher: OnDisconnected() was called by PUN with reason {0}", cause);        
    }
    
    #endregion
    
  5. Launcher 스크립트를 저장합니다.

인터넷 연결 유무와 관계없이 이 씬을 플레이 하면 단계별로 로직을 처리하기 위한 절차를 밟을 수 있습니다. 다음 섹션에서는 UI 구축에 대한 것을 다룰 것 입니다. 지금은 성공적인 연결에 대해서 다루게 될 것 입니다:

따라서, OnConnectedToMaster() 에 다음 호출을 추가하겠습니다.

C#

// #Critical: The first we try to do is to join a potential existing room. If there is, good, else, we'll be called back with OnJoinRandomFailed()  
PhotonNetwork.JoinRandomRoom();

그리고 주석에서 언급 했듯이 룸의 무작위 입장이 실패했을 때 통지를 받게 되며 이 경우에는 룸을 실제로 생성해야 하기 때문에 OnJoinRandomFailed() 라는 PUN 콜백을 스크립트에 구현 하였고 PhotonNetwork.CreateRoom() 을 사용하여 룸을 생성하고 이미 짐작 하셨겠지만 성공적으로 룸에 참가했을 때 OnJoinedRoom() 이라는 PUN 콜백을 받게 됩니다:

C#

public override void OnJoinRandomFailed(short returnCode, string message)
{
    Debug.Log("PUN Basics Tutorial/Launcher:OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom");

    // #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
    PhotonNetwork.CreateRoom(null, new RoomOptions());
}

public override void OnJoinedRoom()
{
    Debug.Log("PUN Basics Tutorial/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room.");
}       

씬을 실행 시키면 PUN 접속에 성공하여 기존의 룸에 참여를 시도하거나 새로 룸을 생성하고 생성된 그 룸에 참여 하게 될 것 입니다.

이 튜토리얼에서는 연결과 룸 참여에 대한 중요한 관점을 다루었으므로 이해가 힘든 부분이 몇 가지 사항들이 있으며 나중에 좀더 알아 볼 필요가 있습니다. 이 튜토리얼들은 PUN 학습을 위한 과정은 아니지만 전반적인 중요한 개념을 다루고 있습니다.

Unity 인스펙터에서의 변수 노출

이미 아실 수도 있고 모르실 수도 있으나 MonoBehaviours 의 public 프로퍼티는 유니티 인스펙터에 자동으로 보이게됩니다. 기본값으로 모든 공개 필드들은 [HideInInspector]로 표시하지 않는한 인스펙터에 표시됩니다. 그리고 공개 필드가 아닌 것을 표시하기를 원한다면 [SerializeField] 속성을 사용할 수 있습니다. 이 사항은 유니티에서 매우 중요한 개념으로, 이 튜토리얼 경우에 있어서 룸당 플레이어의 최대 인원수를 변경할 것이고 인스펙터에 나타나게 하여 코드를 변경하지 않고 설정할 수 있도록 할 것 입니다.

룸에 참여할 수 있는 최대 플레이어 수도 동일한 방식으로 처리 합니다. 소스 코드 내에서 하드코딩하는 것은 바람직하지 않으므로 공개 변수로 만들어서 재컴파일이 필요 없이 나중에 적절한 숫자로 설정 할 수 있습니다.

클래스 선언의 첫 부분에 Private Serializable Fields 영역내에서 추가하겠습니다:

C#

/// <summary>
/// The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created.
/// </summary>   
[Tooltip("The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created")]
[SerializeField]
private byte maxPlayersPerRoom = 4;

그리고 PhotonNetwork.CreateRoom() 호출을 변경하고 이전에 하드코딩되었던 숫자를 새로운 필드로 교체 합니다.

C#

// #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = maxPlayersPerRoom });
launcher script inspector
Launcher 스크립트 인스펙터

이제 우리는 static MaxPlayers 값을 사용할 필요가 없이, 유니티 인스펙터에서 간단하게 설정을 변경하여 실행할 수 있습니다. 스크립트를 열고 편집하여 저장하고 재컴파일될때까지 기다리고 실행을 할 필요가 없다는 것 입니다. 이렇게 하는것이 생산성이 높아지고 유연한 방식입니다.

다음 파트.
이전 파트.

Back to top