로비에 입장하면 포톤 클라우드 서버는 현재 생성된 모든 룸 목록을 전달해준다. 로비에 접속하지 않고 포톤 클라우드 서버에만 접속한 경우에는 룸 목록을 받을 수 없다. PhtonManager 스크립트에 OnRoomListUpdate콜백 함수를 추가해 룸 목록을 전달 받는지를 확인해보자.

 

public  override void OnJoinedRoom() 
 //...중략
public override void OnRoomListUpdate(List<RoomInfo> roomList) {
    foreach (var roomInfo in roomList) {
        // 룸이 삭제된 경우
        Debug.Log($"Room={roomInfo.Name} ({roomInfo.PlayerCount}/{roomInfo.MaxPlayers})");
    }
}
//...중략

OnRoomListUpdate함수는 RoomInfo타입의 데이터를 리스트 자료형으로 넘겨준다. 다음 그림은 새로 빌드한 실행 파일로 세개의 룸을 생성하고 3번째 유저는 ROOM_034 룸에 입장했다. 마지막으로 유니티를 실행해 로비까지만 입장한 상태에서 콘솔뷰를 살펴보자 룸의 목록이 표시되고 ROOM_034 룸의PlayerCount와MaxPlayer가 2/20으로 표시된 것을 알 수 있다.

 

룸목록은 룸정보의 변화가 발생할 때마다 콜백 함수가 호출된다. 다만 삭제된 룸에 대한 정보도 넘어온다.

룸의 삭제여부는 RemovedFromList속성으로 알수 있다. 따라서 룸목록을 Dictionary타입의 자료형으로 관리한다. 앞서 작성했던 OnRoomListUpdate함수를 다음과 같이 수정한다.

 

룸 목록은 룸 이름과 해당 룸을 ScrollView하위에 생성할 RoomItem프리팹을 쌍으로 저장해야 관리하기 편리하기 때문에 dictionary자료형을 사용한다. c#에서 Dictionary를 사용하기 위해서 System.Collections.Generic 네임스페이스를 선언한다.

using System.Collections.Generic;

클래스 선언부에 룸목록에 대한 데이터를 저장할 rooms를 선언한다.

private Dictionary<string, GameObject> rooms = new Dictionary<string, GameObject>();

RoomItem프리팹을 로드할 변수를 Awake()에서 할당한다.

// RoomItem 프리팹 로드
roomItemPrefab = Resources.Load<GameObject>("RoomItem");

룸 목록이 갱신되면 호출되는 OnRoomListUpdate함수의 로직을 간단히 설명하면

 public override void OnRoomListUpdate(List<RoomInfo> roomList) {
        // 삭제된 RoomItem 프리팹을 저장할 임시변수
        GameObject tempRoom = null;

        foreach (var roomInfo in roomList) {
            if (roomInfo.RemovedFromList == true) {  // 룸이 삭제된 경우
                // 딕셔너리에서 룸 이름으로 검색해 저장된 RoomItem 프리팹를 추출
                rooms.TryGetValue(roomInfo.Name, out tempRoom);     
                Destroy(tempRoom); // RoomItem 프리팹 삭제        
                rooms.Remove(roomInfo.Name); // 딕셔너리에서 해당 룸 이름의 데이터를 삭제
            } else // 룸 정보가 변경된 경우
              {
                // 룸 이름이 딕셔너리에 없는 경우 새로 추가
                if (rooms.ContainsKey(roomInfo.Name) == false) {
                    // RoomInfo 프리팹을 scrollContent 하위에 생성
                    GameObject roomPrefab = Instantiate(roomItemPrefab, scrollContent);
                    // 룸 정보를 표시하기 위해 RoomInfo 정보 전달
                    roomPrefab.GetComponent<RoomData>().RoomInfo = roomInfo;

                    // 딕셔너리 자료형에 데이터 추가
                    rooms.Add(roomInfo.Name, roomPrefab);
                } else // 룸 이름이 딕셔너리에 없는 경우에 룸 정보를 갱신
                  {
                    rooms.TryGetValue(roomInfo.Name, out tempRoom); 룸검색해 RoomItem을 tempRoom에 저장
                    tempRoom.GetComponent<RoomData>().RoomInfo = roomInfo;  //룸 정보 전달
                }
            }
            Debug.Log($"Room={roomInfo.Name} ({roomInfo.PlayerCount}/{roomInfo.MaxPlayers})");
        }
    }

버튼 이벤트 동적 연결

OnRoomListUpdate() 함수에서 룸에 대한 정보에 따라서  RoomItem프리팹을 동적으로 생성하거나 이미 만들어진 RoomItem 프리팹에 룸 정보를 저장하고 클릭했을 때 버튼 이벤트에서 룸에 접속하기 위해 새로운 스크립트를 생성하고 이름을 RoomData로 지정한다. 스크립트는 다음과 같이 작성하고 프로젝트 뷰의 Resource 폴더에 있는 RoomItem 프리팹에 추가한다.

using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using TMPro;

public class RoomData : MonoBehaviour {
    private RoomInfo _roomInfo;
    private TMP_Text roomInfoText;   // 하위에 있는 TMP_Text를 저장할 변수
    private PhotonManager photonManager; // PhotonManager 접근 변수

    // 프로퍼티 정의
    public RoomInfo RoomInfo {
        get {
            return _roomInfo;
        }
        set {
            _roomInfo = value;
            // 룸 정보 표시
            roomInfoText.text = $"{_roomInfo.Name} ({_roomInfo.PlayerCount}/{_roomInfo.MaxPlayers})";
            // 버튼 클릭 이벤트에 함수 연결
            GetComponent<UnityEngine.UI.Button>().onClick.AddListener(() => OnEnterRoom(_roomInfo.Name));
        }
    }

    void Awake() {
        roomInfoText = GetComponentInChildren<TMP_Text>();
        photonManager = GameObject.Find("PhotonManager").GetComponent<PhotonManager>();
    }

    void OnEnterRoom(string roomName) {
        // 유저명 설정
        photonManager.SetUserId();
        //룸의 속성 정의
        RoomOptions ro =  new RoomOptions();
        ro.MaxPlayers = 20;  //룸에 입장할 수 있는 최대 접속자수
        ro.IsOpen = true;  // 룸의 오픈 여부
        ro.IsVisible = true; //로비에서 룸 목록에 노출시킬지 여부
        // 룸 접속
        PhotonNetwork.JoinRoom(roomName);
    }
}

RoomData 스크립트는 OnRoomListUpdate에서 룸정보가 갱신될때 마다 접근해 roomInfo 데이터를 넘겨받아서 내부적으로 저장하고하위에 있는 텍스트 UI에 룸 이름과 접속자 정보를 표시한다. 또한 버튼을 클릭했을 때 룸에 접속하는 함수를 람다식으로 연결한다.

 

//룸정보 표시
roomInfoText.text = $"{_roomInfo.Name} ({_roomInfo.PlayerCount}/{_roomInfo.MaxPlayers})";
// 버튼 클릭 이벤트에 함수 연결
GetComponent<UnityEngine.UI.Button>().onClick.AddListener(() => OnEnterRoom(_roomInfo.Name));

예제 게임을 다시 빌드하고 룸을 생성하고 입장한 후 유니티 에디터에서 로비에 입장만 한 상태에서 룸 목록이 표시 되는지 확인해보자. 다음은 룸을 2개 생성하고 2명의 유저는 같은 방에 들어가고 한명의 유저는 혼자 룸에 입장한 시나리오일 때의 룸 목록이다. 룸목록에 표시된 버튼을 클릭하며 해당 룸으로 입장하는지 확인해보자
   

두번 째로 로그인한 유저의 룸 접속 방식은 랜덤 매치메이킹을 통해 자동입장하는것인데 이제 직접 룸을 만든후 다른 유저는 룸의 목록을 조회하고 선택후 입장할 수 있게 해보자

 

Scroll Rect 컴포넌트

먼저 하이라키뷰의 Canvas 하위에 있는 Panel-Login을 선택하고 새로운 PanelUI항목을 추가한다. Panel-RoomList로 변경하고 앵커프리셋을 가로 세로 모두 Stretch로 설정한다. 상하좌우 여백은 다음과 같이 적적히 생성한다.

 Panel-RoomList을 선택하고 UI->ScrollView를 추가한다. 앵커프리셋을 가로세로 Stretch로 설정한다.

ScrollView오브젝트의  ScrollRect컴포넌트의 Horizontal은 언체크한다.

 Scroll View->Viewport 하위의 것들은 실제 스크롤링될 객체이며 룸 목록객체를 정렬하는 역할을 한다.  Content 속성에는 이미 Content 게임오브젝트가 연결됐다.

 

스크롤 객체 생성

로비에 접속했을 때 포톤 클라우드 서버에서 보내주는 룸 목록을 수신받아 동적으로 구성해야 하기 때문에 UI를 디자인하는 시점에서 생성될 룸의 개수가 몇 개인지 알 수 없으며, 가변적일 수 밖에 없다.  따라서 룸 정보를 표시하는 UI를 프리팹으로 미리 만들어두고 로비에 접속했을때 수신된 룸 개수 만큼 반복문을 반복하면서 동적으로 룸정보를 표시하는 프리팹을 Scroll View-ViewPort-Contents하위에 생성하는 방식으로 룸 목록을 구현한다.

하이라키 뷰에서 Contets를 선택하고 컨텍스트 메뉴에서 UI-Image를 선택해 생성한다. 이름을 RoomItem으로 변경하고 Source Image속성은 백그라운드로 설정한다. 백그라운다 이미지는 속성오른쪽 Image Browser를 열어 검색해서 선택한다.

RommItem차일드로 Text Mesh Pro를 추가하고 이름을 Text-RoomInfo로 변경한다

RommItem은 상하 방향으로 스크롤되는 객체이지만 동시에 클릭할 수 있어야한다. 따라서 Button컴포넌트를 추가해야 하지만 동적으로 생성되기 때문에 스크립트에서 처리한다.

 

Grid Layout Group컴포넌트

지금까지 만든 RoomItem을 실행시점에서 동적으로 생성하기 위해 프로젝트뷰의 Resources폴더로 옮겨 프리팹으로 만든다.

실행후 로비에 접속시 이미 5개의 룸이 생성되어 있다면 위 아래로 나란히 정렬되게 해야한다. Layout계열의 컴포넌트를 이용하면 손쉽게 정렬 기능을 구현할 수 있다.

RoomItem을 5개쯤 카피한다. 위치가 겹쳐져있다. 

Content를 선택후 인스펙터에서 AddComponent-[Component][Layout][Grid Layout Group]을 선택해 Grid Layout Group 컴포넌트를 추가하면 자동정렬된다.

Grid Layout Group 속성을 다음과 같이 설정한다.

예제 게임을 실행해 룸 목록을 스크롤해보면 상하로 스크롤 되는걸 확인할 수 있다.

룸에 입장한후 로드된 BattleField씬에서는 아무런 동작도 없이 단순히 씬만 전환된것 뿐이다 .Lobby씬에서 유저명을 입력받아 네트워크상 식별자를 부여한 후 무작위 추출된 룸에 입장하고 BattleField씬으로 전환된것 이다. 따라서 BattleField씬으로 넘어왔다는 것은 이미 룸에 입장한 상태이므로 주인공 캐릭터를 생성하는 로직을 구현해야 한다.

배틀필드 씬을 열어 하이라키뷰에 빈 게임오브젝트를 하나 만들고 이름을 GameManager로 지정한다. 주인공을 생성하는 로직을 처리할 GameManager스크립트를 만들고 추가한다.

using UnityEngine;
using Photon.Pun;

public class GameManager : MonoBehaviourPunCallbacks {

    void Awake() {
        CreatePlayer();
    }

    void CreatePlayer() {
        // 출현 위치 정보를 배열에 저장
        Transform[] points = GameObject.Find("SpawnPointGroup").GetComponentsInChildren<Transform>();
        int idx = Random.Range(1, points.Length);

        // 네트워크상에 캐릭터 생성
        PhotonNetwork.Instantiate("Player",
                                  points[idx].position,
                                  points[idx].rotation,
                                  0);
    }
}

메뉴 [File][Build settings..]를 선택해 Lobby와 BattleField씬을 끌어다 놓는다. 위쪽의 씬이 먼저 실행된다

빌드후 유니티는 로비씬에서 실행한다.

2개의 게임은 로그인 상태이고 로그인 버튼을 먼저 누른 우저가 룸을 생성해 입장한다.

 

하이라키뷰의 Player(Clone)를 선택해 인스펙터뷰에서 PhotonView컴포넌트를 보면 owner속성에 User ID값이 설정된 것을 확인할 수 있다. 

네트워크 게임상 각 플레이어를 식별하기 위해 유저명을 입력받아야 한다. 먼저 UI를 구성하자. 로비 UI구현에 사용할 텍스트는 TextMeshPro를 사용해보자. 메뉴에서 [Window][TextMeshPro][Import TMP Essential Resources]를 선택해 초기화한다.

Canvas를 생성한 후 다음과 같이 로비 UI에서 유저명과 생성할 룸의 이름을 입력받을 Text 항목과 Button을 각각 추가한다. Canvas Scaler의 UI Scale Mode 속성은 Scale With Screen Size로 설정하고 진행한다. UI가 너무 커서 Resolution을 조정후

하이라키뷰의 Canvas를 더블클릭하고 씬뷰를 2D로 만든후 보기 좋게 하고 다음과 같이 배치한다.

로그인 UI구성을 완료한 후 유저명을 입력받아 설정하고 룸을 생성하는 로직을 구현해 본다. PhotoneManager스크립트를 다음과 같이 수정한다.

using TMpro 스페이스를 추가한다.

using System.Collections.Generic;
using UnityEngine;
using TMPro;  //TextMeshPro UI를 위한 네임스페이스
using Photon.Pun;  //포톤 API를 위한 네임스페이스
using Photon.Realtime; //포톤 API를 위한 네임스페이스

 UI 필드를 참조할 변수를 설정한다.

public TMP_InputField userIF; // 유저명을 입력할 TextMeshPro Input Field
public TMP_InputField roomNameIF; // 룸 이름을 입력할 TextMeshPro Input Field

Awake()내의 NickName은 참조처리한다.

//PhotonNetwork.NickName = userId;  //접속 유저의 닉네임 설정

처음 실행했을때 유저명을 자동으로 InputField에 표시하기 위해 PlayerPrefab을 이용 유저명을 로드하고 표시한다. 저장된 유저명이 없다면 랜덤으로 지정한다.

void Start()
{
    // 저장된 유저명을 로드
    userId = PlayerPrefs.GetString("USER_ID", $"USER_{Random.Range(1,21):00}");
    userIF.text = userId;
    // 접속 유저의 닉네임 등록
    PhotonNetwork.NickName = userId;  
}

로그인 UI의 [Login]버튼 또는 [Make Room]버튼을 클릭했을때, 유저명의 변경 사항을 최종확인하고 PlayerPrefs를 사용해 유저명을 저장한후PhotoneNetwork.NickName을 설정한다.

// 유저명을 설정하는 로직
public void SetUserId()
{
    if (string.IsNullOrEmpty(userIF.text))
    {
        userId = $"USER_{Random.Range(1,21):00}";
    }
    else
    {
        userId = userIF.text;
    }

    // 유저명 저장
    PlayerPrefs.SetString("USER_ID", userId);
    // 접속 유저의 닉네임 등록
    PhotonNetwork.NickName = userId;
}

게임 실행시 자동 입장을 방지하기 위해 PhotonNetwork.JoinRandomRoom() 주석처리한다

// 로비에 접속 후 호출되는 콜백 함수
public override void OnJoinedLobby() {
    Debug.Log($"PhotonNetwork.InLobby = {PhotonNetwork.InLobby}");
    //PhotonNetwork.JoinRandomRoom();     수동입장을 위해 주석처리
}

무작위 룸에 입장을 시도했다가 실패했을때 호출되는 OnJoinedRandomFailed 콜백 함수도 룸을 생성하는 로직을 주석 처리하고 OnMakeRoomClick 함수를 호출하도록 수정한다.

// 랜덤한 룸 입장이 실패했을 경우 호출되는 콜백 함수
public override void OnJoinRandomFailed(short returnCode, string message) {
    Debug.Log($"JoinRandom Filed {returnCode}:{message}");
    OnMakeRoomClick();
      // 룸의 속성 정의
    //RoomOptions ro = new RoomOptions();
    //ro.MaxPlayers = 20;     // 룸에 입장할 수 있는 최대 접속자 수
    //ro.IsOpen = true;       // 룸의 오픈 여부
    //ro.IsVisible = true;    // 로비에서 룸 목록에 노출시킬 여부

    // 룸 생성
    //PhotonNetwork.CreateRoom("My Room", ro);
}

룸에 입장한후 호출되는 OnJoinedRoom 콜백함수에서 캐릭터를 생성하는 로직은 주석처리하고 BattleField씬을 호출한다. Phtoneview가 추가된 주인공 캐릭터는 BattleField씬에서 생성한다. 씬을 로딩하는 함수는 유니티가 제공하는 SceneManagement.SceneManager.LoadScene함수대신 PhotoneNetwork.LoadLevel함수를 이용한다.  이 함수는 씬을 로딩전 데이터 송수신을 잠시 멈추고 재개하는 로직이 포함되어 있다. 만일 수동으로 처리한다면 PhotoneNetwork.IsMessageQueueRunning 속성을 false로 지정한후 로딩후 true로 변경해야한다.

씬의 로딩은 마스터 클라이언트만 호출해야 한다. 룸에 입장한 다른 네트워크유저는 PhotonNetwork.AutomaticallySyncScene을 true로 설정했기 때문에 마스터 클라이언트가 다른 씬을 로딩하면 자동으로 씬이 로딩된다.

// 룸에 입장한 후 호출되는 콜백 함수
public override void OnJoinedRoom() {
    Debug.Log($"PhotonNetwork.InRoom = {PhotonNetwork.InRoom}");
    Debug.Log($"Player Count = {PhotonNetwork.CurrentRoom.PlayerCount}");
    foreach (var player in PhotonNetwork.CurrentRoom.Players) {
        Debug.Log($"{player.Value.NickName} , {player.Value.ActorNumber}");
    }
    //출연위치를 배열에 저장
    //Transform[] points = GameObject.Find("SpawnPointGroup").GetComponentsInChildren<Transform>();
    //int idx = Random.Range(1, points.Length);
    //네트워크에 캐릭터 생성
    //PhotonNetwork.Instantiate("Player", points[idx].position, points[idx].rotation, 0);
    // 마스터 클라이언트인 경우에 룸에 입장한 후 전투 씬을 로딩한다.
    if (PhotonNetwork.IsMasterClient) {
        PhotonNetwork.LoadLevel("BattleField");
    }
}

OnLoginClick()함수는 UI의[Login]버튼의 클릭 이벤트에 연결할 함수로 유저명을 설정하고 무작위로 선택한 룸에 입장한다.

public void OnLoginClick() {
    // 유저명 저장
    SetUserId();

    // 무작위로 추출한 룸으로 입장
    PhotonNetwork.JoinRandomRoom();
}

OnMakeRoomClick() 함수 역시 로그인 UI [Maker Room] 버튼에 연결할 함수 이며 입력한 룸 이름으로 룸을 생성한다.

public void OnMakeRoomClick() {
    // 유저명 저장
    SetUserId();

    // 룸의 속성 정의
    RoomOptions ro = new RoomOptions();
    ro.MaxPlayers = 20;     // 룸에 입장할 수 있는 최대 접속자 수
    ro.IsOpen = true;       // 룸의 오픈 여부
    ro.IsVisible = true;    // 로비에서 룸 목록에 노출시킬 여부

    // 룸 생성
    PhotonNetwork.CreateRoom(SetRoomName(), ro);
}

PhotonManager.cs
0.00MB

전체코드

using System.Collections.Generic;
using UnityEngine;
using TMPro;  //TextMeshPro UI를 위한 네임스페이스
using Photon.Pun;  //포톤 API를 위한 네임스페이스
using Photon.Realtime; //포톤 API를 위한 네임스페이스


public class PhotonManager : MonoBehaviourPunCallbacks {
    private readonly string version = "1.0"; // 게임의 버전
    private string userId = "Zack"; // 유저의 닉네임  
                                    
    public TMP_InputField userIF; // 유저명을 입력할 TextMeshPro Input Field
    public TMP_InputField roomNameIF; // 룸 이름을 입력할 TextMeshPro Input Field

    void Awake() {      
        PhotonNetwork.AutomaticallySyncScene = true;  // 마스터 클라이언트의 씬 자동 동기화 옵션
        PhotonNetwork.GameVersion = version; // 게임 버전 설정
        //PhotonNetwork.NickName = userId;  //접속 유저의 닉네임 설정
        Debug.Log(PhotonNetwork.SendRate);// 포톤 서버와의 데이터의 초당 전송 횟수
        PhotonNetwork.ConnectUsingSettings(); // 포톤 서버 접속
    }
    void Start() {
        // 저장된 유저명을 로드
        userId = PlayerPrefs.GetString("USER_ID", $"USER_{Random.Range(1, 21):00}");
        userIF.text = userId;
        // 접속 유저의 닉네임 등록
        PhotonNetwork.NickName = userId;
    }

    // 유저명을 설정하는 로직
    public void SetUserId() {
        if (string.IsNullOrEmpty(userIF.text)) {
            userId = $"USER_{Random.Range(1, 21):00}";
        } else {
            userId = userIF.text;
        }

        // 유저명 저장
        PlayerPrefs.SetString("USER_ID", userId);
        // 접속 유저의 닉네임 등록
        PhotonNetwork.NickName = userId;
    }
    // 룸 명의 입력여부를 확인하는 로직
    string SetRoomName() {
        if (string.IsNullOrEmpty(roomNameIF.text)) {
            roomNameIF.text = $"ROOM_{Random.Range(1, 101):000}";
        }

        return roomNameIF.text;
    }
    public override void OnConnectedToMaster() {
        Debug.Log("Connected to Master!");
        Debug.Log($"PhotonNetwork.InLobby = {PhotonNetwork.InLobby}");
        PhotonNetwork.JoinLobby();
    }

    // 로비에 접속 후 호출되는 콜백 함수
    public override void OnJoinedLobby() {
        Debug.Log($"PhotonNetwork.InLobby = {PhotonNetwork.InLobby}");
        //PhotonNetwork.JoinRandomRoom();     수동입장을 위해 주석처리
    }

    // 랜덤한 룸 입장이 실패했을 경우 호출되는 콜백 함수
    public override void OnJoinRandomFailed(short returnCode, string message) {
        Debug.Log($"JoinRandom Filed {returnCode}:{message}");
        OnMakeRoomClick();
          // 룸의 속성 정의
        //RoomOptions ro = new RoomOptions();
        //ro.MaxPlayers = 20;     // 룸에 입장할 수 있는 최대 접속자 수
        //ro.IsOpen = true;       // 룸의 오픈 여부
        //ro.IsVisible = true;    // 로비에서 룸 목록에 노출시킬 여부

        // 룸 생성
        //PhotonNetwork.CreateRoom("My Room", ro);
    }

    // 룸 생성이 완료된 후 호출되는 콜백 함수
    public override void OnCreatedRoom() {
        Debug.Log("Created Room");
        Debug.Log($"Room Name = {PhotonNetwork.CurrentRoom.Name}");
    }

    // 룸에 입장한 후 호출되는 콜백 함수
    public override void OnJoinedRoom() {
        Debug.Log($"PhotonNetwork.InRoom = {PhotonNetwork.InRoom}");
        Debug.Log($"Player Count = {PhotonNetwork.CurrentRoom.PlayerCount}");
        foreach (var player in PhotonNetwork.CurrentRoom.Players) {
            Debug.Log($"{player.Value.NickName} , {player.Value.ActorNumber}");
        }
        //출연위치를 배열에 저장
        //Transform[] points = GameObject.Find("SpawnPointGroup").GetComponentsInChildren<Transform>();
        //int idx = Random.Range(1, points.Length);
        //네트워크에 캐릭터 생성
        //PhotonNetwork.Instantiate("Player", points[idx].position, points[idx].rotation, 0);
        // 마스터 클라이언트인 경우에 룸에 입장한 후 전투 씬을 로딩한다.
        if (PhotonNetwork.IsMasterClient) {
            PhotonNetwork.LoadLevel("BattleField");
        }
    }
    #region UI_BUTTON_EVENT

    public void OnLoginClick() {
        // 유저명 저장
        SetUserId();

        // 무작위로 추출한 룸으로 입장
        PhotonNetwork.JoinRandomRoom();
    }

    public void OnMakeRoomClick() {
        // 유저명 저장
        SetUserId();

        // 룸의 속성 정의
        RoomOptions ro = new RoomOptions();
        ro.MaxPlayers = 20;     // 룸에 입장할 수 있는 최대 접속자 수
        ro.IsOpen = true;       // 룸의 오픈 여부
        ro.IsVisible = true;    // 로비에서 룸 목록에 노출시킬 여부

        // 룸 생성
        PhotonNetwork.CreateRoom(SetRoomName(), ro);
    }

    #endregion
}

스크립트를 작성 저장후  로그인 UI의 InputField(TMP)-UserID와 InputField(TMP)-Room을 PhtonManager의 User IF, Room Name IF에 각각 드래그해 연결한다.

하이라키뷰의 [Canvas][Button-Login]을 선택하고 인스펙터의 OnClick에서 +를 눌러 이벤트를 하나 만들어 주고 하이라키뷰에서 PhotonManager를 끌어다 놔주면 활성화되는데 

No Fuction을 선택해 OnLoginClick()을 지정해준다.

같은 방법으로 Room Button도 OnMakerRoomClick을 지정해준다.

게임룸 입장

이제 실행해보면 씬이 로드되지 않았다는 에러가 나서

File-BuildSetting에서 우리가 만든 2개의 씬을 추가해주었다

이제 실행해보면 User ID 입력필드에 무작위 이름이 표시된다.

여러분의 이름을 직접입력하고 [Login]버튼을 누르면 입장되고 콘솔뷰에 유저명과 룸에 접속한 접속자 수가 표시된다.

또한 씬이 BattleField로 변경된것을 확인할 수 있다.

룸에 입장한후 로드된 BattleField씬에서는 아무런 동작도 없이 단순히 씬만 전환된것 뿐이다 .Lobby씬에서 유저명을 입력받아 네트워크상 식별자를 부여한 후 무작위 추출된 룸에 입장하고 BattleField씬으로 전환된것 이다. 따라서 BattleField씬으로 넘어왔다는 것은 이미 룸에 입장한 상태이므로 주인공 캐릭터를 생성하는 로직을 구현해야 한다.

로비는 네트워크에 접속한 모든 플레이어가 대기하며 방을 생성하거나 다른방에 입장할 수 있는 기능을 제공해야 한다. 포톤 클라우드에서 로비에 접속해야만 현재 생성된 룸의 정보를 서버로부터 받아올수 있다. 예제 게임에서는 이미 로비에 입장했기 때문에 룸 정보를 받을 준비가 끝났다.

 로비씬 제작

로비를 구성하기 위해 현재 열려있는 SampleScene을 저장후 프로젝트뷰 Scenes폴더에서 이름을 BattleField로 변경한후 복제해 로비씬으로 사용한다.  씬을 선택하고  Ctrl-D으로 씬을 복제하낟. 복제한 씬의 이름을 Lobby로 변경한다.

이제부터 포톤 클라우드에 접속하는 과정은 Lobby씬에서 처리한다. 따라서 BattleField씬에 있는 PhotonManager 게임오브젝트는 삭제한다.

이제 로비씬을 선택해 수정한다.

로비 씬에서는 주인공을 추적하는 기능이 필요없기 때문에 CM vcam1 게임오브젝트와 Main Camera의 cinemachine Brain 컴포넌트도 삭제한다.

프로젝트뷰의 AngryBotResource/Prefabs폴더의 Player를 하이라키뷰에 추가하고 적절한 위치에 배치한다. 또한 게임 뷰를 보면서 Main Camera 위치와 각도를 다음과 같이 설정한다.

*  씬뷰에서 적당한 화면 구도를 잡는다. 하이라뷰의 Main Camera를 선택후 Ctrl-Shit-F를 누르면 Align with view가 실행된다.

로비씬에는 시각적 효과를 위해 피사계심도와 색보정 효과를 추가해보겠다.

로비씬은 BattleField씬을 복사했기 때문에 Global Volume은 같은 Profile을 공유하고 있다.  따라서 New옆의 Clone을 클릭해 Lobby씬의 Post Processing 효과를 위한 새로운 Profile을 만들어야 한다.

Add Override로 Depth of Field, Color Adjustment후처리를 추가한다 

효과는 씬뷰에서는 확인이 안되고 게임뷰에서 확인할 수 있다

 

+ Recent posts