Unity - PUN2

땡구의 개발일지·2025년 7월 11일

Unity마스터

목록 보기
62/78
post-thumbnail

유니티에서 제공하는 네트워크 기능인 PUN2를 통해 네트워크 게임을 구현해보자

🔁 포톤 PUN2

  • Photon Unity Networking(PUN)

photon 기능들을 유니티에서 사용할 수 있게 만든 SDK이다.

🔧 매치메이킹

  • 서버에 존재하는 게임 룸 리스트를 확인하면서 참가.
  • 서버에게 매칭되는 룸을 선정받는 빠른참가.
  • 친구를 따라 룸에 입장하는 기능.

룸 : 같은 룸에 참가한 클라이언트들은 동기화를 통해 실시간 네트워크 구성이 가능하다. 실시간 동기화 작업이 중요하다

🔧 실시간 동기화

  • 객체 동기화 : 데이터 스트림으로 변수 데이터 전달을 통해 이루어진다.
  • RPC : Remote Procedure Call. 모든 클라이언트가 동일한 함수를 호출 할 수 있도록 전달하는 원격 함수 호출
  • 커스텀 프로퍼티 : 룸과 플레이어의 정보를 캐시해두고 동기화

🚩 포톤 앱 생성

포톤 서버를 이용하려면 을 먼저 생성해야 한다.


먼저 로그인 한다. 아이디가 없으면 만들고 접속하자

어플리케이션을 만들어보자

만들고자 하는 용도에 맞게 작성한다

이러면 서버 어플리케이션이 생성된다. App ID를 복사하자


🚩 유니티에 PUN 추가하기

에셋 스토어에서 PUN2를 추가해줘야 한다. 추가한 이후, 유니티에서 패키지매니저로 추가할 수 있다.


패키지를 설치하고 나면 위와같은 화면이 나온다. 여기에 아까 만든 서버의 App ID를 집어넣는다.


🚩 네트워크 테스트

기능이 제대로 되는지 테스트를 진행해본다

📜 NetworkManager

아래와 같이 스크립트를 작성한다

using UnityEngine;
using Photon.Pun;

public class NetworkManager : MonoBehaviourPunCallbacks
{
    void Start()
    {
        PhotonNetwork.ConnectUsingSettings(); // 접속 시도
    }

    public override void OnConnected() // 접속됐을 경우 Callback
    {
		base.OnConnected();
		Debug.Log($"연결");
    }

    public override void OnConnectedToMaster() // 마스터 서버 접속 시 Callback
    {
        base.OnConnectedToMaster();
        Debug.Log("마스터연결");
    }
}

📌 씬에 추가

작성한 스크립트를 씬의 빈 오브젝트에 컴포넌트로 추가하고 실행해보자.

인터넷 연결, 서버 접속을 확인하는 순으로 진행하는 것을 로그를 통해 알 수 있다.

🔧 서버 세팅


여기서 Locate PhotonServerSettings를 클릭

해당 스크립터블 오브젝트를 통해 서버 세팅이 가능하다


🚩 포톤 룸&로비

포톤은 매치메이킹 기능으로 룸과 로비를 구성하는 기능을 제공한다.

  • 로비 : 다른 플레이어들이 생성한 방 목록을 확인하고, 방을 만들거나 참가할 수 있다.

  • : 방에 참여한 플레이어들을 확인하고, 상태를 최신화 할 수 있음. 이후 게임에서 방의 플레이어들만 참여하게 할 수 있다. 이러한 과정들을 준비하는 공간.

💡 포톤의 네트워크 구현 방법

클라이언트(유저들)와 서버 간의 통신을 통해 요청, 반응을 구현한다.

🔧 클라이언트 ➡ 서버 요청

PhotonNetwork 클래스 사용. 해당 클래스의 함수들을 사용해서, 클라이언트가 서버에게 요청을 한다.

PhotonNetwork.ConnectUsingSettings();   // 접속 시도 요청
PhotonNetwork.Disconnect();             // 접속 해제 요청

PhotonNetwork.CreateRoom("RoomName");   // 방 생성 요청
PhotonNetwork.JoinRoom("RoomName");     // 방 입장 요청
PhotonNetwork.LeaveRoom();              // 방 퇴장 요청

PhotonNetwork.JoinLobby();              // 로비 입장 요청
PhotonNetwork.LeaveLobby();             // 로비 퇴장 요청

PhotonNetwork.LoadLevel("SceneName");   // 씬 전환 요청

bool isConnected = PhotonNetwork.IsConnected;           // 접속 여부 확인
bool isInRoom = PhotonNetwork.InRoom;                   // 방 입장 여부 확인
bool isLobby = PhotonNetwork.InLobby;                   // 로비 입장 여부 확인
ClientState state = PhotonNetwork.NetworkClientState;   // 클라이언트 상태 확인
Player player = PhotonNetwork.LocalPlayer;              // 포톤 플레이어 정보 확인
Room players = PhotonNetwork.CurrentRoom;               // 현재 방 정보 확인

🔧 서버 ➡ 클라이언트

MonoBehaviourPunCallbacks 클래스 사용. 해당 클래스를 상속받아 구현하고, 해당 클래스의 함수들을 통해 클라이언트에게서 받은 요청에 대한 서버의 반응을 다시 클라이언트에게 전달한다.

public class NetworkManager : MonoBehaviourPunCallbacks
{
  public override void OnConnected() { }                          // 포톤 접속시 호출됨
  public override void OnConnectedToMaster() { }                  // 마스터 서버 접속시 호출됨
  public override void OnDisconnected(DisconnectCause cause) { }  // 접속 해제시 호출됨

  public override void OnCreatedRoom() { }    // 방 생성시 호출됨
  public override void OnJoinedRoom() { }     // 방 입장시 호출됨
  public override void OnLeftRoom() { }       // 방 퇴장시 호출됨
  public override void OnPlayerEnteredRoom(Player newPlayer) { }  // 새로운 플레이어가 방 입장시 호출됨
  public override void OnPlayerLeftRoom(Player otherPlayer) { }   // 다른 플레이어가 방 퇴장시 호출됨
  public override void OnCreateRoomFailed(short returnCode, string message) { }   // 방 생성 실패시 호출됨
  public override void OnJoinRoomFailed(short returnCode, string message) { }     // 방 입장 실패시 호출됨

  public override void OnJoinedLobby() { }    // 로비 입장시 호출됨
  public override void OnLeftLobby() { }      // 로비 퇴장시 호출됨
  public override void OnRoomListUpdate(List<RoomInfo> roomList) { }  // 방 목록 변경시 호출됨
}

🚩 커스텀 프로퍼티

멀티 플레이 환경에서 필요한 정보들이 있다. 룸, 플레이어의 경우 각각 아래와 같다.

  • : 이름, 최대 참여가능 인원, 현재 참여인원, 공개/비공개, 비밀번호, , 개인전 / 팀전 여부

  • 플레이어 : 닉네임, 아이디, 방장여부, 레디 여부, 캐릭터,

위와 같은 정보들은 포톤에서 기본적으로 구현되어 있지 않다. 이러한 기능들을 사용하기 위해서 커스텀 프로퍼티가 제공된다. 이를 통해 추가하고자 하는 정보의 이름과 값(딕셔너리)을 설정하여 같은 게임을 플레이하는 클라이언트들과 정보공유를 통해 동기화가 가능하다.

🔧 ExitGames.Client.Photon.HashTable

포톤에서 제공하는 HashTable자료구조. C#HashTableDictionary와 사용 방법이 같다. 네트워크 전송을 위해 직렬화 처리가 추가되었다.

📜 커스텀 프로퍼티 스크립트

Room room = PhotonNetwork.CurrentRoom;  // 현재 참가한 룸을 확인

// 룸 커스텀 프로퍼티 설정
ExitGames.Client.Photon.Hashtable roomProperty = new ExitGames.Client.Photon.Hashtabl> ();
roomProperty["Map"] = "Select Map";
room.SetCustomProperties(roomProperty);

// 룸 커스텀 프로퍼티 확인
string curMap = (string)room.CustomProperties["Map"];

Player player = PhotonNetwork.LocalPlayer;  // 자신 플레이어를 확인

// 플레이어 커스텀 프로퍼티 설정
ExitGames.Client.Photon.Hashtable playerProperty = new ExitGames.Client.Photon> Hashtable();
playerProperty["Ready"] = true;
player.SetCustomProperties(playerProperty);

// 플레이어 커스텀 프로퍼티 확인
bool ready = (bool)player.CustomProperties["Ready"];

📜 커스텀 프로퍼티 콜백

아래 함수들은 커스텀 프로퍼티의 콜백함수들이다. 프로퍼티의 변동사항을 추적해서 제어할 수 있다.

public class NetworkManager : MonoBehaviourPunCallbacks
{
    public override void OnRoomPropertiesUpdate(ExitGames.Client.Photon.Hashtable propertiesThatChanged)
    {
        // 현재 참여한 방의 프로퍼티가 업데이트시 호출됨
    }

    public override void OnPlayerPropertiesUpdate(Player targetPlayer, ExitGames.Client.Photon.Hashtable changedProps)
    {
        // 같은 방의 플레이어의 프로퍼티가 업데이트시 호출됨
    }
}

🔁 ParrelSync

네트워크 서비스를 테스트하기 위해서는 매번 빌드를 해서 테스트 해야한다. 이러한 불편함을 해소하기 위해서 ParrelSync를 이용한다.

추가적으로, 한 대의 컴퓨터로 다른 클라이언트의 역할을 할 수 있게 프로젝트를 클론해주는 기능도 있다. 이를 통해 여러 대의 클라이언트를 테스트하는 효과를 볼 수 있다.

✅ 설치


링크한 Github에서 릴리즈를 다운 받으면 유니티 패키지가 있다. 이 유니티 패키지를 추가해주거나, 해당 깃허브 링크를 패키지 매니저에서 추가해주면 된다.

여기서 Github 주소를 추가해주면 된다.

설치하면 위와같이 탭에 ParrelSync가 생기게 된다. Clones Manager를 눌러보자.

여기서 Add new clone을 누르면 된다.

🔧 클론 실행

Open in New Editor를 누르면 클론의 에디터가 실행된다. 원본 에디터에서 플레이를 누르고, 클론 에디터도 플레이를 누르면 둘 다 서버에 접속해서 개별적인 클라이언트로 작동하게 된다. ParrelSync덕에 빌드없이 테스트가 가능하다.


🏠 마스터 클라이언트

온라인 게임에서 하나의 룸에서 게임에 대한 권한을 가진 구성원을 방장이라고 할 수 있다. 이러한 권한 설정을 포톤에서는 Master Client로 구현한다.

public class NetworkManager : MonoBehaviourPunCallbacks
{
   public void GameStart()
   {
      // 자신 플레이어가 방장이 아닌 경우 반환하여 아래의 코드가 실행되지 않도록 함
      if (PhotonNetwork.LocalPlayer.IsMasterClient == false)
           return;
    
      // 방장만이 실행할 수 있는 소스코드
      PhotonNetwork.AutomaticallySyncScene = true;    // 모든 방구성원이 같은 씬으로 > 이동하도록 동기화함
       PhotonNetwork.LoadLevel("GameScene");          // 네트워크를 통해 씬을 이동하도록 > 요청함
    }
}

🚩 네트워크 구현

이제 직접 프로젝트를 생성하고 구현해보자.

✅ 구현 예시

구현하기에 앞서 결과물이 어떤지 확인하고 시작하자.

🔁 로딩 창


로딩 창에서 네트워크 접속을 확인한다. 마스터 서버까지 접속이 완료될 경우, 닉네임 입력 창으로 넘어간다. Current State는 현재 상황을 실시간으로 보여준다.

📝 닉네임 입력 창


게임에서 사용할 닉네임을 설정하는 창이다.
닉네임 입력을 완료하면, 로비 창으로 넘어간다.

🌍 로비 창


로비 창에서는 방 생성, 방목록 확인 및 참가가 가능하다.

🏠 룸 창


방을 생성하면, 방을 생성한 클라이언트는 방으로 들어간 후 방장이 된다. 동시에 다른 클라이언트에서는 생성된 방을 목록에서 확인 가능하다.

  • 맵 설정 : 게임에 사용될 맵을 설정할 수 있다.

  • 레디 : 레디 상태를 변경할 수 있다.

  • 스타트 : 방 구성원들과 동기화 후, 게임화면으로 넘어감. 방장만 누를 수 있고, 모든 구성원에 레디 상태여야 한다.

  • 떠나기 : 방을 떠날 수 있다. 구성원이 2명 이상이고, 방장이 떠날 경우 남은 사람이 방장이 된다. 아무도 남지 않을 경우, 방
    은 삭제된다.

  • 채팅 : 채팅을 칠 수 있다. 채팅 내용은 방 구성원 모두에게 공유된다.

  • 플레이어 패널 : 현재 방에 접속 중인 구성원 수 만큼 패널이 늘어난다. 방장일 경우 별도로 표시하는 UI를 활성화 한다.

📜 NetworkManager

네트워크 매니저로 현재 네트워크 연결 상태를 관리한다. 로딩 창, 닉네임 입력 창, 로비 창 입장까지의 단계가 표현된 상태다.

using UnityEngine;
using UnityEngine.UI;
using Photon.Pun; // 네트워크 기능을 쓰기 위한 네임스페이스
using Photon.Realtime; // 동기화에 쓰이는 네임스페이스
using TMPro;

public class NetworkManager : MonoBehaviourPunCallbacks
{
    [SerializeField] private GameObject loadingPanel;
    [SerializeField] private GameObject nicknamePanel;
    [SerializeField] private TextMeshProUGUI stateText;
    [SerializeField] private TMP_InputField nicknameText;
    [SerializeField] private Button nicknameAdmitBtn;
    void Start()
    {
        PhotonNetwork.ConnectUsingSettings();// 인터넷을 통해 서버에 접속을 요청
        nicknameAdmitBtn.onClick.AddListener(NicknameAdmit);
    }

    private void Update()
    {
    	// 여러 필드들을 확인할 수 있음.
        stateText.text = $"Current State : {PhotonNetwork.NetworkClientState}"; 
    }

    public void NicknameAdmit() // 아이디 작성 완료 후, 버튼을 누르면 수행. 로비로 전환을 서버에 요청한다
    {
        if (!string.IsNullOrWhiteSpace(nicknameText.text))
        {
            PhotonNetwork.NickName = nicknameText.text; // 플레이어의 닉네임 설정 가능
            PhotonNetwork.JoinLobby(); // 서버에 로비 접속 요청
        }
        else
        {
            Debug.Log("이름을 입력해주세요");
        }
    }
    
    public override void OnConnected() // 서버 접속 요청에 대한 응답이 와서 접속한 경우
    {
        base.OnConnected();
        Debug.Log($"연결");
    }

    public override void OnConnectedToMaster() // ConnectUsingSettings를 사용하면, 마스터 서버까지 접속을 요청한다.
    {
        base.OnConnectedToMaster();
        Debug.Log("마스터연결");
        loadingPanel.SetActive(false); // 접속에 성공했으니, 로딩창을 끄고 닉네임 입력 창으로 전환
    }
    
    public override void OnJoinedLobby() // 로비로 이동요청 후, 서버의 응답이 와서 로비에 접속할 경우
    {
        base.OnJoinedLobby();
        nicknamePanel.SetActive(false); 
        Debug.Log($"로비 참가");
    }


    public override void OnDisconnected(DisconnectCause cause) // 서버 접속에 실패했을 때 수행
    {
        base.OnDisconnected(cause);
        PhotonNetwork.ConnectUsingSettings(); // 재연결 시도
    }
}

📌 테스트 결과

🔧 방을 만들고 참가하기

방을 생성함과 동시에 해당 방에 참가하는 기능을 구현해본다. 쓸 수 있는 프로퍼티와 함수들은 API Reference를 참고하자.

위의 하이라키 창과 같이 UI 구성을 한다. 그리고 아래와 같이 NetworkManager 스크립트를 수정한다.

📝 NetworkManager

[SerializeField] private GameObject lobbyPanel;
[SerializeField] private TMP_InputField roomNameText;
[SerializeField] private Button roomNameAdmitBtn;

void Start()
{
    roomNameAdmitBtn.onClick.AddListener(CreateRoom); // 버튼에 방 생성 이벤트 연결
}


public void CreateRoom() // 방 생성
{
    if (string.IsNullOrEmpty(roomNameText.text))
    {
        Debug.LogWarning("방 이름 입력이 없음");
        return;
    }
    roomNameAdmitBtn.interactable = false; // 더블클릭 같은 문제를 미연에 방지함
    RoomOptions roomOptions = new RoomOptions{MaxPlayers = 8}; // 룸 옵션에는 여러 사용할 수 있는 옵션들이 있다.
    PhotonNetwork.CreateRoom(roomNameText.text, roomOptions); // 최대 8명까지 접속 가능한 방으로 설정
    roomNameText.text = null; // 방 생성 후 인풋 필드 초기화
    Debug.Log("방을 생성 시도");
}

public override void OnCreatedRoom()
{
    base.OnCreatedRoom();
    lobbyPanel.SetActive(false);
    Debug.Log("방을 만들기 완료. 방에 참가 시도");
}

public override void OnJoinedRoom()
{
    base.OnJoinedRoom();
    Debug.Log("방에 참가 완료");
}

📌 테스트 결과


중복된 이름의 방 생성 차단, 방에서 로비로 나올 경우의 초기화 작업은 나중에 다시 하도록 한다.

🔧 방 목록 만들기


로비창에서 방 목록에 방이 생길 때마다 추가해보자.

ContentVertical Layout Group을 추가해준다. 이를 통해, 룸 리스트 아이템이 추가될 때마다 자동으로 정렬되서 아래에 추가된다.

📝 스크립트 수정

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

public override void OnRoomListUpdate(List<RoomInfo> roomList) // 방 목록 변경시 호출됨. RoomInfo에는 방의 정보들이 담김 
{
    foreach (RoomInfo info in roomList) // 방 정보를 순회
    {
        if (info.RemovedFromList) // 방이 꽉 찼거나, 닫혔거나, 숨겨졌을 경우
        {
            if (roomListItemDict.TryGetValue(info.Name, out GameObject obj)) // 방 생성 시마다 추가했던 딕셔너리에 있을 경우 딕셔너리에서도 삭제
            {
                Destroy(obj); // 오브젝트 삭제
                roomListItemDict.Remove(info.Name); // 딕셔너리에서 삭제
            }

            continue; // 다음방 정보로 넘어감
        }
        if (roomListItemDict.ContainsKey(info.Name)) // 딕셔너리에 방이 있음
        {
            roomListItemDict[info.Name].GetComponent<RoomListItem>().Init(info); //플레이어의 수가 변경되는 경우 등
        }
        else // 딕셔너리에 방이 없음. 로비에 새로 입장, 방이 새로 생성
        {
           GameObject roomListItem = Instantiate(roomListItemPrefab);// 룸리스트 오브젝트 생성
           roomListItem.transform.SetParent(roomListItemParent); //스크롤뷰의 컨텐트에 넣어줌
           roomListItemDict.Add(info.Name, roomListItem); // 딕셔너리에 추가함
           roomListItem.GetComponent<RoomListItem>().Init(info); // 초기화
        }
    }
}  

📦 RoomListItem

로비에서 보이는 방 목록에 들어갈 UI 프리팹이다. 해당 스크립트를 부착해서 RoomInfo를 넘기면 초기화 할 수 있게 한다.

public class RoomListItem : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI roomNameText;
    [SerializeField] private TextMeshProUGUI playerCountText;
    [SerializeField] private Button joinButton;

    private string roomName;
    
    public void Init(RoomInfo info)
    {
        roomName = info.Name;
        roomNameText.text = $"Room Name : {roomName} ";
        playerCountText.text = $"{info.PlayerCount} / {info.MaxPlayers}";

        joinButton.onClick.AddListener(JoinRoom);
    }

    public void JoinRoom()
    {
        joinButton.onClick.RemoveAllListeners();
        PhotonNetwork.JoinRoom(roomName);
    }
}

✅ 테스트

플레이해서 방을 생성하는 단계까지 진행 후, ParrelSync로 만든 클론 에디터로 방이 생성되었는지 확인한다.

🏠 룸 내부 구현

멀티플레이로 방을 생성해서 들어왔으니, 내부를 꾸며보자

📦 룸 패널



플레이어 슬롯이 추가되는 패널에는 Grid Layout Group을 추가한다.
왼쪽 아래의 인풋 필드는 추후에 채팅 창으로 구현한다.

📝 Network Manager

public override void OnJoinedRoom() // 방 입장 시 호출
{
    base.OnJoinedRoom();
    lobbyPanel.SetActive(false);
    roomManager.PlayerPanelSpawn();
    Debug.Log("방에 참가 완료");
}
    public override void OnPlayerEnteredRoom(Player newPlayer) // 새로운 플레이어가 방 입장시 호출됨. 본인은 호출안됨
{
    if (newPlayer != PhotonNetwork.LocalPlayer) //본인이 아닐 경우에만 수행. 이미 처리가 되어있지만 안전장치 역할을 함
    {
        roomManager.PlayerPanelSpawn(newPlayer);
    }
}
public override void OnPlayerLeftRoom(Player otherPlayer) // 본인이 아닌, 다른 플레이어가 방 퇴장시 호출됨
{
	if(otherPlayer != PhotonNetwork.LocalPlayer) // 이미 시스템 상 막혀있지만 혹시모를 경우를 대비한 예외처리
    roomManager.PlayerPanelDestroy(otherPlayer);
}

📝 RoomManager

using Hashtable = ExitGames.Client.Photon.Hashtable;

public class RoomManager : MonoBehaviour
{
    public Dictionary<int,PlayerPanelItem> playerDict = new Dictionary<int, PlayerPanelItem>();
    
    private void Start()
    {
        startBtn.onClick.AddListener(GameStart);
        leaveBtn.onClick.AddListener(LeaveRoom);
        mapLeftBtn.onClick.AddListener(ClickLeftMapButton);
        mapRightBtn.onClick.AddListener(ClickRightMapButton);
    }
    public void PlayerPanelSpawn()// 내가 새로 입장했을 때 호출. OnJoinedRoom()에서 수행됨.
    {
        foreach (Player player in PhotonNetwork.PlayerList) // 현재 방에 접속한 모든 플레이어(PlayerList)
        {
            PhotonNetwork.AutomaticallySyncScene = true; // 마스터 클라이언트를 따라 동시에 같은 레벨을 로드함
            if (!PhotonNetwork.IsMasterClient) // 본인이 호스트가 아닐 경우, 권한 뺏음
            {
                startBtn.interactable = false;
                mapLeftBtn.interactable = false;
                mapRightBtn.interactable = false;
            }
            // 플레이어 슬롯을 생성해서 패널에 추가함. 초기화 과정
            GameObject obj = Instantiate(playerPanelItemPrefabs);
            obj.transform.SetParent(playerPanelContoent);
            obj.GetComponent<PlayerPanelItem>().Init(player);
            PlayerPanelItem item = obj.GetComponent<PlayerPanelItem>(); // 초기화
            playerDict.Add(player.ActorNumber,item); // 방에 들어온 순서대로 ActorNumber가 올라감. 재입장해도 새로 배정 받음
        }
    }
    public void PlayerPanelSpawn(Player player) 
    // Player : 플레이어와 관련한 정보들이 담겨있음. 현재 방에 새로운 플레이어 입장시 호출
    {
        if (playerDict.TryGetValue(player.ActorNumber, out PlayerPanelItem playerPanelItem))
        {
            startBtn.interactable = false;
            mapLeftBtn.interactable = false;
            mapRightBtn.interactable = false;
            playerPanelItem.Init(player);
        }
        
        GameObject obj = Instantiate(playerPanelItemPrefabs);
        obj.transform.SetParent(playerPanelContoent);
        obj.GetComponent<PlayerPanelItem>().Init(player);
        PlayerPanelItem item = obj.GetComponent<PlayerPanelItem>(); // 초기화
        playerDict.Add(player.ActorNumber,item); // 방에 들어온 순서대로 ActorNumber가 올라감.
    }

    public void PlayerPanelDestroy(Player player) // 플레이어가 떠날 시 호출
    {
        if (playerDict.TryGetValue(player.ActorNumber, out PlayerPanelItem item))
        {
            Destroy(item.gameObject); // 플레이어가 떠났으니, 슬롯 삭제
            playerDict.Remove(player.ActorNumber); // 딕셔너리에서도 제거
        }
        else
        {
            Debug.LogWarning($"플레이어가 딕셔너리에 없음: {player.ActorNumber}");
        }
    }
}

✅ 테스트


방장이 방을 생성 후, 클론 클라이언트가 해당 방에 입장했을 때, 방장 클라이언트의 룸 패널 상황

클론 클라이언트가 게임 종료 시, 방장의 룸 패널 상황

🏠 룸 떠나기

방을 떠날 때 해야 하는 작업들이다.

📜 PlayerSlot

플레이어가 입장/퇴장 시 추가/삭제되는 프리팹의 스크립트

public class RoomListItem : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI roomNameText;
    [SerializeField] private TextMeshProUGUI playerCountText;
    [SerializeField] private Button joinButton;
    
    private string roomName;
    public void Init(RoomInfo info)
    {
        roomName = info.Name;
        roomNameText.text = $"Room Name : {roomName} ";
        playerCountText.text = $"{info.PlayerCount} / {info.MaxPlayers}";
        joinButton.onClick.AddListener(JoinRoom);
    }

    public void JoinRoom()
    {
        joinButton.onClick.RemoveAllListeners();
        PhotonNetwork.JoinRoom(roomName);
    }
}

📝 RoomManager

방을 떠날 때 작업을 추가해주자. 해당 방의 정보들을 지운다.

public void LeaveRoom() // 본인이 방을 떠날 때 호출
{
    // 현재 있었던 방의 정보를 지움
    foreach (Player player in PhotonNetwork.PlayerList)
    {
        Destroy(playerDict[player.ActorNumber].gameObject);
    }

    playerDict.Clear();
    PhotonNetwork.LeaveRoom();
}

❌ 방 재접속 문제


만들어진 방에 접속 후, 떠난다. 다시 방에 접속하려고 하면 해당 에러가 발생한다. 분명 OnJoinedLobby() 콜백함수 이후에 한 것인데도 불구하고, 에러가 발생한다.

현재 상태를 보면 Joining이라고 나온다.
분명 방에 접속 전에는 상태가 JoinedLobby였는데, 왜 에러는 로비에 접속을 못했다고 하는 것인지 모르겠다. Joining이 로비에 대한 접속인 것 같다.

일시정지를 풀면, 정상적으로 방에 접속은 된다.

📌 해결

알아보니, 콜백으로 로비에 참가했어도, 실제로는 로비에 참가 이후 후처리가 남아있다고 한다. 이 후처리들이 끝나기도 전에 다시 방에 참가하려고 하니까 문제가 생긴 것. 아래와 같이 방에 참가하려는 것에 조건을 걸어주자.

클라이언트의 상태가 JoinedLobby로 바뀌는 것은 후처리까지 끝난 이후다.

public void JoinRoom()
{
    if (PhotonNetwork.NetworkClientState == ClientState.JoinedLobby)
    {
        PhotonNetwork.JoinRoom(roomName);
    }
    joinButton.onClick.RemoveAllListeners();
}
profile
개발 박살내자

0개의 댓글