1 - 로비
서버 접속, 룸 접근과 생성
우선 이 튜토리얼의 핵심인 Photon 클라우드 서버에 connect하는 것과 룸에 참가 또는 필요시 생성에 대한 것을 알아 보겠습니다.
새로운 씬을 생성하여
Launcher.unity
로 저장합니다.새로운 C# 스크립트인
Launcher
를 생성합니다.Hierarchy 에서 빈 GameObject를 생성하고
Launcher
로 이름을 부여해줍니다.GameObject
Launcher
에Launcher
C# 스크립트를 붙여줍니다.아래와 같이 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 } }
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일 때 MasterClient는 PhotonNetwork.LoadLevel()을 호출 할 수 있고 모든 연결된 플레이어들은 동일한 레벨을 자동적으로 로드 할 것입니다.
이 시점에서 Launch 씬을 저장할 수 있고 PhotonServerSettings (유니티 메뉴 Window/Photon Unity Networking/Highlight Photon Server Settings에서 선택)을 오픈하여 PUN 로깅을 "Full" 로 설정하도록 합니다:
그리고나서 플레이를 누를 수 있습니다.
유니티 콘솔에서 로그를 볼 수 있을 것 입니다.
이중 한 가지 주목할 것은 "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()으로 연습해 보겠습니다.
C# 스크립트
Launcher
를 편집합니다.기본 클래스를 MonoBehaviour 에서 MonoBehaviourPunCallbacks으로 변경합니다.
C#
public class Launcher : MonoBehaviourPunCallbacks {
파일 상단의 클래스 정의앞에
using Photon.Realtime;
을 추가합니다.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
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 });
이제 우리는 static MaxPlayers 값을 사용할 필요가 없이, 유니티 인스펙터에서 간단하게 설정을 변경하여 실행할 수 있습니다. 스크립트를 열고 편집하여 저장하고 재컴파일될때까지 기다리고 실행을 할 필요가 없다는 것 입니다.
이렇게 하는것이 생산성이 높아지고 유연한 방식입니다.