Unity/FPS Project

[Photon] 방 입장 및 레디 상태 추가

SiJun-Park 2025. 6. 7. 14:41

이번에는 바로 게임을 시작하는 것보다. 

 

방에 입장을 해서 레디를 한 후 게임을 시작하는 것이 유저 입장에선 준비 할 시간이 된다고 생각을 하여서 구현을 하게 되었습니다.

 

UserIcon Prefab

닉네임 부분은 유저의 닉네임을 가져오고, 레디는 레디 버튼을 누르게 된다면 레디가 되도록 하였습니다.

 

Room

방은 Lobby Scene에서 방을 만들게 된다면 Scene이동 보단 Canvas의 On/Off로 구현을 하였고, Scroll view로 작업을 하였습니다.

 public void Start() {

     if (PlayerPrefs.HasKey("NickName"))
         PhotonNetwork.NickName = PlayerPrefs.GetString("NickName");

     PhotonNetwork.ConnectUsingSettings();
 }

RoomManager부터 수정을 한다면, 닉네임을 정확하게 불러왔는지 미지수니깐 닉네임을 확실하게 불러오고 연결을 해줍니다.

 

    void ClearRoomIcons()
    {
        foreach (Transform child in Roomparent)
        {
            Destroy(child.gameObject);
        }
    }

    void CreateIcon(Player player, int number)
    {
        GameObject room = Instantiate(Resources.Load<GameObject>("UserIcon"));
        room.transform.SetParent(Roomparent);
        room.GetComponent<RoomNickNameManager>().SetNickNameText(player.NickName, number);
    }

처음에 만들었던 UserIcon을 삭제하거나 추가하는 함수입니다.

 

    public override void OnJoinedRoom() {
        EnterRoom(PhotonNetwork.IsMasterClient ? "Wait" : "Ready");

        ClearRoomIcons();

        foreach (var player in PhotonNetwork.PlayerList)
        {
            CreateIcon(player, player.ActorNumber);
        }


        if (PhotonNetwork.IsMasterClient) AddData("PlayerReady", PhotonNetwork.LocalPlayer.ActorNumber, true); // 방장은 레디상태
    }

마스터 클라이언트면 가장 먼저 wait또는 Ready라고 버튼 text에 적히게 하고, 한번 방에 Icon을 Clear를 해줍니다.

 

만약에 겹쳐서 들어올 경우 처리를 해주어야 하니 플레이어 수 만큼 아이콘을 만들어줍니다.

 

또한 방장은 항상 준비상태로 설정을 해주었습니다.

 

    public override void OnPlayerEnteredRoom(Player newPlayer) => CreateIcon(newPlayer, newPlayer.ActorNumber); // 새로운 유저 입장

새로운 유저가 들어오면 아이콘을 생성을 해줍니다.

 

    public void SetNickNameText(string nickName, int number) { NickName.text = nickName; UserNumber = number; }

    public int GetUserNumber() => UserNumber;
    public void SetReady(bool b) => Ready.gameObject.SetActive(b);

위는 NickNameManager입니다.  UserIcon에 넣었습니다.

 

새로운 닉네임을 지정할때 닉네임과 유저 로컬 번호를 저장하게 하였습니다.

 

그 이유는 나중에 UI 업데이트를 한다고 하였을 때 유저 번호에 따른 아이콘들을 다 업데이트 해주기 위해서입니다.

 

    public override void OnPlayerEnteredRoom(Player newPlayer) => AddData("PlayerReady", newPlayer.ActorNumber, false);

    public override void OnPlayerLeftRoom(Player leftPlayer) => RemoveData("PlayerReady", leftPlayer.ActorNumber);

새로운 유저가 입장하면 PlayerReady라는 테이블을 추가해주고

유저가 나가게 된다면 PlayerReady라는 테이블을 제거해줍니다

*마스터 클라이언트만 가능하게 마스터 클라이언트가 받는 함수로만 작업을 하였습니다.

 

    public void OnReadyOrStartButton()
    {
        if (PhotonNetwork.IsMasterClient)
        {
            if (Button_text.text.Equals("Go"))
            {
                Debug.Log("게임 시작");
            }
        }
        else
        {
            if (Button_text.text.Equals("Ready"))
            {
                Button_text.text = "UnReady";
                photonView.RPC("ChangeReady", RpcTarget.MasterClient, true, PhotonNetwork.LocalPlayer.ActorNumber);
            }
            else
            {
                Button_text.text = "Ready";
                photonView.RPC("ChangeReady", RpcTarget.MasterClient, false, PhotonNetwork.LocalPlayer.ActorNumber);
            }

        }
    }

버튼에 따라 역할을 넣었고, Ready상태라면 RPC로 Master Client에 보내어 값을 수정해주도록 하였습니다.

 

    [PunRPC]
    void ChangeReady(bool b, int number)
    {
        if (!PhotonNetwork.IsMasterClient) return;
        UpdateData("PlayerReady",number, b);
    }

넘겨받은 마스터는 해당 정보를 Update해줍니다.

 

    bool AllPlayersReady()
    {
        var room= PhotonNetwork.CurrentRoom.CustomProperties;

        if (!room.ContainsKey("PlayerReady"))
            return false; // 아무도 레디 안한 상태

        PhotonHashTable table = room["PlayerReady"] as PhotonHashTable;
        if (table == null)
            return false;

        foreach (Player p in PhotonNetwork.PlayerList)
        {
            if (!table.ContainsKey(p.ActorNumber))
                return false; // 아직 레디 안함

            if (!(bool)table[p.ActorNumber])
                return false; // false거나 잘못된 값
        }


        return true; 
    }

모든 유저가 준비상태인지 체크하기 위해서 작성을 하였습니다

 

테이블에 PlayerReady가 없다면 오류인 방이니 false로 넘겨주고, 플레이어 리스트를 돌면서 Ready를 했는지 체크를 해줍니다.

    void UpdateReadyUI()
    {
        var room = PhotonNetwork.CurrentRoom.CustomProperties;
        if (room.ContainsKey("PlayerReady"))
        {
            PhotonHashTable table =room["PlayerReady"] as PhotonHashTable;
            foreach (Transform child in Roomparent)
            {
                RoomNickNameManager ready = child.GetComponent<RoomNickNameManager>();
                if (ready == null || table == null) continue;
                int actorNumber = ready.GetUserNumber(); // 유저 정보 가져오기
                bool isReady = false;
                if (table.ContainsKey(actorNumber)) isReady = (bool)table[actorNumber];
                ready.SetReady(isReady);
            }
        }
    }

UI 업데이트 입니다.

 

RoomParent안의 자식들을 조사하며 RoomNickNameManager를 들고옵니다.

그 이후에 유저 정보를 가져와서 해당 테이블에 유저 정보의 Ready 정보를 가져옵니다.

 

만약에 true라면 Ready object를 활성화 시켜주고, 아니라면 비활성화 시켜줍니다.

 

    public override void OnRoomPropertiesUpdate(PhotonHashTable propertiesThatChanged)
    {
        UpdateReadyUI();
        if (PhotonNetwork.IsMasterClient)
        {
            if (propertiesThatChanged.ContainsKey("PlayerReady"))
            {
                if (AllPlayersReady())
                {
                    Button_text.text = "Go";
                }
                else
                {
                    if (Button_text.text.Equals("Go")) Button_text.text = "Wait";
                }
            }
        }
    }

마지막으로 properties가 update가 됐다는 것은, 레디나 언레디 상태를 만든 것이니 

 

UI를 업데이트 해주고, Master Client인 경우는 모든 유저가 준비가 됐는지 체크를 해주어

 

모두가 레디가 되었으면 게임 시작을 할 수 있게 합니다.

 

 

결과