PUN Classic (v1), PUN 2 and Bolt are in maintenance mode. PUN 2 will support Unity 2019 to 2022, but no new features will be added. Of course all your PUN & Bolt projects will continue to work and run with the known performance in the future. For any upcoming or new projects: please switch to Photon Fusion or 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> 태그의 형태로 구조적인 주석을 에디터가 자동적으로 생성해주게됩니다.
        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으로 변경합니다.

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

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

        #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() 에 다음 호출을 추가하겠습니다.

// #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 콜백을 받게 됩니다:

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 영역내에서 추가하겠습니다:

/// <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() 호출을 변경하고 이전에 하드코딩되었던 숫자를 새로운 필드로 교체 합니다.

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

다음 파트.
이전 파트.


기술문서 TOP으로 돌아가기