Photon PUN2 + Chat + Voice2 (APK)

Version : Unity 2021.3.5f1

Photon Chat은 PUN2 에 포함되어 있다.

처음 접속을 하면 로비로 이동하여 Room목록을 확인

Room에 접속하면 자동으로 채팅 및 음성이 연결되는 구조

포톤 기능 RND 목적으로 간단하게 만든 앱이라서 발생할 수 있는 모든 문제를 해결하지 않음

(예를 들어 채팅중 네트워크 오류, 입장 불가 등… )

무료 라이센스라서 20명 이상 동시 접속 시 터질 수 있음

Photon PUN2 + Chat

APP.CS

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using Newtonsoft.Json;


namespace Com.Lycos7560
{
    public struct PlayerData
    {
        public string NickName;
        public string UserGuid;
        public PlayerData(string _nicknName, string _userGuid = null)
        {
            NickName = _nicknName;
            UserGuid = _userGuid;
        }
    }

    public class APP : SingletonMonobehaviour<APP>
    {

        protected override void Awake()
        {
            base.Awake();
            if (CheckPlayerData()) {
                PlayerPrefs.DeleteAll();
                LoadPlayerNickName();
            }
            else {
                PlayerPrefs.DeleteAll();
                PlayerPrefs.SetString("NickName", string.Empty);
                PlayerPrefs.SetString("UserGuid", Guid.NewGuid().ToString());
                SavePlayerNickName();
            }
        }

        // 로컬에 데이터가 존재하는지 확인 
        private bool CheckPlayerData()
        {
            bool exists = File.Exists(Application.persistentDataPath + "/PlayerInfo.json");
            if (exists) Debug.Log("Existing User");
            else Debug.Log("NewUser");
            return exists;
        }

        // 플레이어의 정보를 로컬에 저장
        public void SavePlayerNickName()
        {
            PlayerData _PlayerData = new (PlayerPrefs.GetString("NickName"), PlayerPrefs.GetString("UserGuid"));
            var json = JsonConvert.SerializeObject(_PlayerData);
            File.WriteAllText(Application.persistentDataPath + "/PlayerInfo.json", json);
        }

        // 로컬에 저장된 플레이어의 정보를 불러온다.
        public void LoadPlayerNickName()
        {
            var _Json = File.ReadAllText(Application.persistentDataPath + "/PlayerInfo.json");
            var _Info = JsonConvert.DeserializeObject<PlayerData>(_Json);
            PlayerPrefs.SetString("NickName", _Info.NickName);
            PlayerPrefs.SetString("UserGuid", _Info.UserGuid);
        }
    }
}

Connection.CS

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

namespace Com.Lycos7560
{
    public class Connection : MonoBehaviourPunCallbacks
    {
        public GameObject _ConnetMain;

        public TMP_InputField _InputFieldName;

        public CanvasGroup ConnetGroup;
        public Button ConnetBTN;

        public TMP_Text ConnectionStatusText;

        public override void OnEnable()
        {
            if (PlayerPrefs.GetString("NickName") != string.Empty)
                _InputFieldName.text = PlayerPrefs.GetString("NickName");
            else _InputFieldName.text = string.Empty;
            

        }


        private void Awake()
        {
            _InputFieldName.onValueChanged.AddListener((_value) =>
            {
                if (_value.Length > 0 || _value != string.Empty)
                {
                    ConnetGroup.alpha = 1f;
                    ConnetGroup.interactable = true;
                    ConnetGroup.blocksRaycasts = true;
                }
                else
                {
                    ConnetGroup.alpha = 0.3f;
                    ConnetGroup.interactable = false;
                    ConnetGroup.blocksRaycasts = false;
                }
            });

            ConnetBTN.onClick.AddListener(() => {
                ConnetGroup.alpha = 0.3f;
                ConnetGroup.interactable = false;
                ConnetGroup.blocksRaycasts = false;
                _InputFieldName.interactable = false;
                PlayerPrefs.SetString("NickName", _InputFieldName.text);
                APP.Instance.SavePlayerNickName();
                ConnectionStatusText.text = "Connecting . . .";
                NetworkManager.Instance.Connect();
            });

            _ConnetMain.SetActive(true);
        }

        private void Start()
        {
            NetworkManager.Instance.OnDisconnectedAction += ConnectionOnDisconnected;
            NetworkManager.Instance.OnJoinedLobbyAction += ConnectionOnJoinedLobby;
            NetworkManager.Instance.OnChangeNetworkClientState += OnChangeNetworkClientState;

            if (PlayerPrefs.GetString("NickName") != string.Empty)
                _InputFieldName.text = PlayerPrefs.GetString("NickName");
            else _InputFieldName.text = string.Empty;
        }

        // 액션 등록
        private void ConnectionOnJoinedLobby()
        {
            Debug.Log("Connet OnJoinedLobby()");
            _ConnetMain.SetActive(false);

        }

        // 액션 등록
        public void ConnectionOnDisconnected()
        {
            Debug.Log("연결 종료");
            _ConnetMain.SetActive(true);
            ConnetGroup.alpha = 1f;
            ConnetGroup.interactable = true;
            ConnetGroup.blocksRaycasts = true;
            _InputFieldName.interactable = true;
            _InputFieldName.text = PlayerPrefs.GetString("NickName");
        }


        private void OnChangeNetworkClientState(ClientState _clientState)
        {
            ConnectionStatusText.text = _clientState.ToString();
        }
    }
}

Lobby.CS

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using Photon.Pun;
using Photon.Realtime;

namespace Com.Lycos7560
{
    public class Lobby : MonoBehaviourPunCallbacks
    {
        public GameObject _LobbyMain;
        public Button _ExitBtn;
        public Button _CreateRoomBtn;
        public TMP_Text LobbyPeopleCountText;

        public Transform _Content;

        public List<TypedLobbyInfo> _LobbyInfoList;

        public GameObject RoomItemPrefab;
        private List<RoomItem> _RoomItemList = new();

        private TypedLobby _CntLobby => PhotonNetwork.CurrentLobby;
        private List<RoomInfo> _CntRoomList;

        private Dictionary<string, RoomInfo> _SachedRoomList = new();
        private Coroutine _LobbyCoroutine;

        public GameObject _RoomJoinPopUp;
        public Button _PopUpCloseBtn;
        public TMP_Text _RoomJoinFailText;

        private void Awake()
        {
          
            _ExitBtn.onClick.AddListener(() => {
                NetworkManager.Instance.DisconnectMainServer();
            });

            _CreateRoomBtn.onClick.AddListener(() => {
                _RoomJoinPopUp.SetActive(true);
                NetworkManager.Instance.CreateRoomWithNickName(false); 
            });

            _PopUpCloseBtn.onClick.AddListener(() => {
                _RoomJoinPopUp.SetActive(false);
            });

            for (int i = 0; i < 20; i++)
            {
                RoomItem _RoomItem = Instantiate<GameObject>(RoomItemPrefab, _Content).GetComponent<RoomItem>();
                _RoomItem.ChangeRoomInfo("Defalut", 0, 0);
                _RoomItemList.Add(_RoomItem);
                _RoomItemList[i].gameObject.SetActive(false);
                _RoomItem.JoinRoomBtn.onClick.AddListener(() => {
                    _RoomJoinPopUp.SetActive(true);
                    _RoomJoinFailText.text = "Try to access the chat room.";
                    _PopUpCloseBtn.gameObject.SetActive(false);
                    NetworkManager.Instance.JoinRoom(_RoomItem.RoomName.text);
                });
            }

            _LobbyMain.SetActive(false);
        }

        private void Start()
        {
            // 액션 등록
            NetworkManager.Instance.OnDisconnectedAction += LobbyOnDisconnected;
            NetworkManager.Instance.OnJoinedLobbyAction += LobbyOnJoinedLobby;
            NetworkManager.Instance.OnJoinRoomFailedAction += LobbyOnJoinRoomFailed;
            NetworkManager.Instance.OnJoinedRoomAction += LobbyOnJoinedRoom;
            NetworkManager.Instance.OnLeftRoomAction += LobbyOnLeftRoom;
        }



        #region ActionFuctions

        private void LobbyOnJoinedLobby()
        {
            _LobbyMain.SetActive(true);

            _RoomJoinFailText.text = "Attempt to connect . . .";
            _PopUpCloseBtn.gameObject.SetActive(false);
            _SachedRoomList.Clear();

            if (_LobbyCoroutine != null)
            {
                StopCoroutine(_LobbyCoroutine);
                _LobbyCoroutine = null;
            }
            _LobbyCoroutine = StartCoroutine(UpdateLobbyCoroutine());

            LobbyPeopleCountText.text = string.Format($"Lobby People Count : {PhotonNetwork.CountOfPlayersOnMaster}");
        }

        private void LobbyOnDisconnected()
        {
            if (_LobbyCoroutine != null)
                StopCoroutine(_LobbyCoroutine);
            _LobbyCoroutine = null;
            _LobbyMain.SetActive(false);
        }

        private void LobbyOnJoinedRoom()
        {
            _RoomJoinPopUp.SetActive(true);
            _RoomJoinFailText.text = "Attempt to connect . . .";
            _PopUpCloseBtn.gameObject.SetActive(false);
            _LobbyMain.SetActive(false);

            if (_LobbyCoroutine != null) 
                StopCoroutine(_LobbyCoroutine);
            _LobbyCoroutine = null;
        }

        private void LobbyOnLeftRoom()
        {
            _LobbyMain.SetActive(true);
            _RoomJoinFailText.text = "Attempt to connect . . .";
            _PopUpCloseBtn.gameObject.SetActive(false);
            _SachedRoomList.Clear();

            if (_LobbyCoroutine != null) {
                StopCoroutine(_LobbyCoroutine);
                _LobbyCoroutine = null;
            }
            _LobbyCoroutine = StartCoroutine(UpdateLobbyCoroutine());
            UpdateLobbyRoomList();
        }

        private void LobbyOnJoinRoomFailed(short returnCode, string message)
        {
            PhotonNetwork.JoinLobby(_CntLobby);
            _RoomJoinPopUp.SetActive(true);
            _PopUpCloseBtn.gameObject.SetActive(true);
            _RoomJoinFailText.text = string.Format($"returnCode : {returnCode} ,  {message}");
            UpdateLobbyRoomList();
        }

        #endregion


        #region  MonoBehaviourPunCallbacks
        public override void OnRoomListUpdate(List<RoomInfo> roomList)
        {
            _CntRoomList = roomList;
            UpdateLobbyRoomList();
        }

        public override void OnCreatedRoom()
        {
            // 카운트 리셋
            NetworkManager.Instance.CreateRoomWithNickName(true);
        }


        public override void OnCreateRoomFailed(short returnCode, string message)
        {
            NetworkManager.Instance.CreateRoomWithNickName(false);
        }


        #endregion



        /// <summary>
        /// 일정 시간마다 로비에 Room과 있는 로비 인원을 업데이트
        /// 목적이 Chatting을 사용 가능할 때까지 대기 
        /// </summary>
        private IEnumerator UpdateLobbyCoroutine()
        {
            yield return new WaitUntil(() => NetworkManager.Instance._ChatClient != null && NetworkManager.Instance._ChatClient.CanChat);
            _RoomJoinPopUp.SetActive(false);
            _PopUpCloseBtn.gameObject.SetActive(true);

            WaitForSeconds _Delay = new WaitForSeconds(10f);
            yield return _Delay;

            while (true)
            {
                LobbyPeopleCountText.text = string.Format($"Lobby People Count : {PhotonNetwork.CountOfPlayersOnMaster}");
                PhotonNetwork.JoinLobby(_CntLobby);
                UpdateLobbyRoomList();
                yield return _Delay;
                if (_LobbyMain.activeSelf == false) break;
            }

            _LobbyCoroutine = null;
        }

        private void UpdateLobbyRoomList()
        {
            for (int i = 0; i < _CntRoomList.Count; i++)
            {
                RoomInfo info = _CntRoomList[i];
                if (info.RemovedFromList) _SachedRoomList.Remove(info.Name);
                else _SachedRoomList[info.Name] = info;
            }

            for (int j = 0; j < _RoomItemList.Count; j++)
            {
                _RoomItemList[j].gameObject.SetActive(false);
                _RoomItemList[j].ChangeRoomInfo("Defalut", 0, 0);
            }

            for (int j = 0; j < _CntRoomList.Count; j++)
            {
                RoomInfo info = _CntRoomList[j];
                if (info.PlayerCount == 0) continue;
                _RoomItemList[j].gameObject.SetActive(true);
                _RoomItemList[j].ChangeRoomInfo(info.Name, info.PlayerCount, info.MaxPlayers);
            }
        }


    }
}

Chatting.CS

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using Photon.Pun;
using Photon.Chat;
using ExitGames.Client.Photon;

namespace Com.Lycos7560
{
    public class Chatting : MonoBehaviourPunCallbacks, IChatClientListener
    {
        public ChatClient _ChatClient;

        public GameObject ChattingMain;

        public Button _CloseChattingMainBtn;

        public TMP_Text _ServerStateText;

        public TMP_InputField _ChatInputField;

        public Button _ChatSendBtn;

        public TMP_Text _ChattingContentText;
        public TMP_Text SubscribeUsersText;

        public ChatChannel _CntChatChannel;

        #region IChatClientListener CallBacks

        public void OnGetMessages(string channelName, string[] senders, object[] messages)
        {
            for (int i = 0; i < senders.Length; i++)
            {
                string sender = senders[i];
                string message = messages[i].ToString();

                string chatMessage = $"[{sender}]: {message}";

                // 채팅 메시지 추가
                _ChattingContentText.text += chatMessage + "\n";
            }
        }

        // 개인 메시지(귓속말) 미구현
        public void OnPrivateMessage(string sender, object message, string channelName)
        {
            Debug.Log("Private message from [" + sender + "]: " + message);
        }

        // 접속한 Room의 이름과 관련된 채널 구독이 성공 CallBack
        public void OnSubscribed(string[] channels, bool[] results)
        {
            _ChattingContentText.text = string.Empty;
            // 채널을 캐싱해준다. (구독 완료된 시점)
            if (_ChatClient.TryGetChannel("Room_" + PhotonNetwork.CurrentRoom.Name, out _CntChatChannel))  

            // 나가기 버튼 활성화
            _CloseChattingMainBtn.gameObject.SetActive(true); 

            // 첫 화면 출력
            _ChattingContentText.text += " Welcome to Photon Chat. \n";
            UpdateSubscribeUsersText();
            /* 디버깅
            for (int i = 0; i < channels.Length; i++)
                Debug.Log("Subscribed to Channel: " + channels[i]); 
            */
        }

        // 접속한 Room의 이름과 관련된 채널 구독을 해제 CallBack
        public void OnUnsubscribed(string[] channels)
        {
            // 구독 해제와 동시에 Clear
            _ChattingContentText.text = string.Empty;
            _CntChatChannel = null;
            /* 디버깅
            for (int i = 0; i < channels.Length; i++)
                Debug.Log("Unsubscribed from Channel: " + channels[i]);
            */
        }

        // 디버그 CallBack 미구현
        public void DebugReturn(DebugLevel level, string message)
        {
            Debug.Log("Debug: " + level + " - " + message);
        }

        // 디버그 CallBack 미구현
        public void OnDisconnected()
        {
            Debug.Log("Disconnected from Chat Server");
        }

        // 디버그 CallBack 미구현
        public void OnChatStateChange(ChatState state)
        {
            _ServerStateText.text = string.Format($"Server State : {state}");
        }

        // 디버그 CallBack 미구현
        public void OnStatusUpdate(string user, int status, bool gotMessage, object message)
        {
            Debug.Log("Status Update - User: " + user + ", Status: " + status);
        }

        //  어떤 유저가 해당 채널을 구독 CallBack
        public void OnUserSubscribed(string channel, string user)
        {
            // 메시지 창에 어떤 유저가 구독했는지 출력
            string message = $"<color=#06FF00>'{user}' has joined '{channel}'</color>";
            _ChattingContentText.text += message + "\n";
            UpdateSubscribeUsersText();
        }

        //  어떤 유저가 해당 채널을 구독 해제 CallBack
        public void OnUserUnsubscribed(string channel, string user)
        {
            // 메시지 창에 어떤 유저가 구독을 해제했는지 출력
            string message = $"<color=#FF0000> '{user}' has left '{channel}'</color>";
            _ChattingContentText.text += message + "\n";
            UpdateSubscribeUsersText();
        }


        #endregion


        #region MonoBehaviour

        private void Awake()
        {         
            ChattingMain.SetActive(false);
            _CloseChattingMainBtn.onClick.AddListener(() => { 
                  PhotonNetwork.LeaveRoom();
            });
            _ChatSendBtn.onClick.AddListener(() => {
                if (_ChatInputField.text == string.Empty) return;
                else {
                    SendRoomMessage(_ChatInputField.text);
                    _ChatInputField.text = string.Empty;
                }
            });
        }

        private void Start()
        {
            // 액션 등록
            NetworkManager.Instance.OnJoinedRoomAction += ChattingOnJoinedRoom;
            NetworkManager.Instance.OnLeftRoomAction += ChattingOnLeftRoom;
            NetworkManager.Instance.OnJoinedLobbyAction += ChattingOnJoinedLobby;
        }

        private void Update()
        {
            // !!필수!!
            // ChatClient의 콜백 처리를 위해 주기적으로 호출
            if (_ChatClient != null)
                _ChatClient.Service();
        }

        #endregion



        #region ActionFuctions

        /// <summary>
        /// 로비에 접속하면 Chatting Server를 활성화 시킨다.
        /// </summary>
        private void ChattingOnJoinedLobby()
        {
            // 구독 전에 나가는 것을 방지
            _CloseChattingMainBtn.gameObject.SetActive(false); 

            if (_ChatClient == null) {
                // 로비에 접속한 후 ChatClient 초기화 및 연결
                _ChatClient = new ChatClient(this);
                _ChatClient.Connect(PhotonNetwork.PhotonServerSettings.AppSettings.AppIdChat, PhotonNetwork.AppVersion, new Photon.Chat.AuthenticationValues(PhotonNetwork.NickName));
                Debug.LogFormat($"Chatting Server 초기화 및 연결 시작");
            }
        }

        /// <summary>
        ///  Room에 접속하면 실행되는 함수
        ///  Room의 이름과 관련된 채팅 서버에 접속한다.
        /// </summary>
        private void ChattingOnJoinedRoom()
        {
            _ChattingContentText.text = string.Empty;
            ChattingMain.SetActive(true);
            string _Channel = "Room_" + PhotonNetwork.CurrentRoom.Name; // 가입할 채널 이름 배열
            _ChatClient.Subscribe(_Channel, 0, -1, new ChannelCreationOptions { PublishSubscribers = true } ); // 채널에 구독
            Debug.LogFormat($"{"Room_" + PhotonNetwork.CurrentRoom.Name} :: 구독");

        }

        /// <summary>
        /// Room에서 나갈 경우 실행되는 함수
        /// </summary>
        private void ChattingOnLeftRoom()
        {
            string[] _Channels = new string[] { _CntChatChannel.Name };
            _ChatClient.Unsubscribe(_Channels);
            _CntChatChannel = null;
            ChattingMain.SetActive(false);
        }

        #endregion

        /// <summary>
        /// 구독한 Channel에 메시지를 보냅니다.
        /// </summary>
        /// <param name="message"></param>
        private void SendRoomMessage(string message)
        {
            if (_ChatClient != null && _ChatClient.CanChat) {
                //  _ChatClient가 구독한 방에 메시지를 보냅니다.
                _ChatClient.PublishMessage("Room_" + PhotonNetwork.CurrentRoom.Name, message);
            }
        }

        /// <summary>
        /// 체널의 있는 유저들의 정보를 갱신합니다.
        /// </summary>
        private void UpdateSubscribeUsersText()
        {
            if (_ChatClient != null && _CntChatChannel != null) {
                var _Users = _CntChatChannel.Subscribers;
                SubscribeUsersText.text = string.Empty;
                foreach (var _user in _Users)
                    SubscribeUsersText.text += _user + "  ";               
            }
            else SubscribeUsersText.text = "There is a problem with the network.";
           
        }
    }






}

NetworkManager.CS

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

namespace Com.Lycos7560
{
    public class NetworkManager : MonoBehaviourPunCallbacks
    {
        public static NetworkManager Instance;

        public Connection _Connection;
        public Chatting _Chatting;
        public Lobby _Lobby;


        public ChatClient _ChatClient { get { return _Chatting._ChatClient; } }

        #region Private Serializable Fields
        #endregion

        #region Private Fields

        /// <summary>
        /// 이 클라이언트의 버전 번호입니다.
        /// 사용자는 gameVersion에 의해 서로 분리되어 있습니다 
        /// (이를 통해 변경 사항을 변경할 수 있습니다).
        /// </summary>
        string gameVersion = "1";

        /// <summary>
        /// 룸당 최대 플레이어 수입니다. 
        /// 방이 꽉 차면 새로운 플레이어가 참여할 수 없기 때문에 새로운 방이 만들어집니다.
        /// </summary>
        [Tooltip("방이 꽉 차면 새로운 플레이어가 참여할 수 없기 때문에 새로운 방이 만들어집니다.")]
        [SerializeField]
        private byte maxPlayersPerRoom = 5;


        /// <summary>
        /// 현재 NetworkClientState의 상태를 나타냅니다.
        /// </summary>
        [Tooltip("현재 NetworkClientState의 상태를 나타냅니다. NetworkClientStateCheck 함수를 통하여 업데이트 됩니다")]
        [SerializeField]
        private ClientState _CntNetworkClientState = ClientState.PeerCreated;

        public ClientState CntNetworkClientState
        {
            get => _CntNetworkClientState;
            set
            {
                if (_CntNetworkClientState != value)
                    OnChangeNetworkClientState?.Invoke(value);
                _CntNetworkClientState = value;
            }
        }

        [Tooltip("방생성을 시도한 횟수입니다.")]
        [SerializeField]
        private int _CreateRoomCnt = 1;

        #endregion

        #region public Fields

        #endregion

        public UnityAction<ClientState> OnChangeNetworkClientState;

        public UnityAction OnDisconnectedAction;

        public UnityAction OnJoinedLobbyAction;

        public UnityAction OnJoinedRoomAction;

        public UnityAction<short, string> OnJoinRoomFailedAction;

        public UnityAction OnLeftRoomAction;



        #region MonoBehaviour CallBacks

        /// <summary>
        /// MonoBehavior 메서드는 초기 초기화 단계에서 Unity에 의해 GameObject에 호출되었습니다.
        /// </summary>
        void Awake()
        {
            Instance = this;
            // #Critical
            // 이렇게하면 PhotonNetwork를 사용할 수 있습니다.
            // 마스터 클라이언트의 LoadLevel()과 동일한 룸에 있는 모든 클라이언트의 LoadLevel이 자동으로 동기화됩니다
            PhotonNetwork.AutomaticallySyncScene = true;
            UpdateCntNetworkClientState();

        }

        /// <summary>
        /// 마스터 서버에 진입한 후에 바로 실행
        /// </summary>
        public override void OnConnectedToMaster()
        {
            UpdateCntNetworkClientState();
            PhotonNetwork.JoinLobby();
            PhotonNetwork.NickName = PlayerPrefs.GetString("NickName");
        }

        /// <summary>
        /// 접속이 종료되었을 경우 실행된다.
        /// </summary>
        /// <param name="cause"></param>
        public override void OnDisconnected(DisconnectCause cause)
        {

            if (cause == DisconnectCause.DisconnectByServerLogic)
            {
                // 로비 입장 실패
                Debug.LogFormat($"<color=red> Failed to enter lobby : {cause} </color>");
            }
            else
            {
                // 기타 연결 끊김 원인(예: 인터넷 연결 끊김)
                Debug.Log($"<color=red> Disconnected from server : {cause} </color>");
            }
            UpdateCntNetworkClientState();
            OnDisconnectedAction?.Invoke();
        }

        public override void OnJoinedLobby()
        {
            UpdateCntNetworkClientState();
            OnJoinedLobbyAction?.Invoke();
        }

        public override void OnJoinedRoom()
        {

            string userId = PhotonNetwork.LocalPlayer.UserId;
            Debug.Log("Joined Room - User ID: " + userId);

            OnJoinedRoomAction?.Invoke();

        }

        public override void OnJoinRoomFailed(short returnCode, string message)
        {
            
            OnJoinRoomFailedAction?.Invoke(returnCode, message);
        }


        public override void OnLeftRoom()
        {
            OnLeftRoomAction?.Invoke();
        }

        public void CreateRoomWithNickName(bool _CntReset)
        {
            if (!_CntReset)
            {
                string _RoomStringBase = PlayerPrefs.GetString("NickName");
                string _UserGuid = PlayerPrefs.GetString("UserGuid");
                if (_UserGuid.Length >= _CreateRoomCnt)
                    PhotonNetwork.CreateRoom(_RoomStringBase + "_#" + _UserGuid[.._CreateRoomCnt++], new RoomOptions { MaxPlayers = maxPlayersPerRoom, CleanupCacheOnLeave = true, PlayerTtl = 1000 });
                else Debug.Log("방생성 실패(너무 많은 생성 실패)");
                
            }
            else _CreateRoomCnt = 1;
            

        }

        #endregion



        #region Public Methods

        /// <summary>
        /// 연결 프로세스를 시작합니다.
        /// </summary>
        public void Connect()
        {
            // 연결되었다면 로비에 참여
            if (PhotonNetwork.IsConnected) PhotonNetwork.JoinLobby(new TypedLobby("MainLobby", LobbyType.Default));
            else
            {
                PhotonNetwork.GameVersion = gameVersion;
                PhotonNetwork.ConnectUsingSettings();
            }
        }

        public void JoinRoom(string _roomName)
        {
            PhotonNetwork.JoinRoom(_roomName);
        }

        public void DisconnectMainServer()
        {
            PhotonNetwork.Disconnect();
        }

        public void UpdateCntNetworkClientState()
        {
            CntNetworkClientState = PhotonNetwork.NetworkClientState;
        }

        #endregion

    }
}

RoomItem.CS

using UnityEngine;
using UnityEngine.UI;
using TMPro;

namespace Com.Lycos7560
{
    public class RoomItem : MonoBehaviour
    {
        public TMP_Text RoomName;
        public TMP_Text PeopleNumber;
        public Button JoinRoomBtn;

        public void ChangeRoomInfo(string _name, int _cnt, int _max)
        {
            RoomName.text = _name;
            PeopleNumber.text = string.Format($"{_cnt} / {_max}");
        }



    }
}

Photon PUN2 + Chat + Voice

Chatting.CS (수정)

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using Photon.Pun;
using Photon.Chat;
using Photon.Voice.Unity;
using ExitGames.Client.Photon;

namespace Com.Lycos7560
{
    public class Chatting : MonoBehaviourPunCallbacks, IChatClientListener
    {
        public ChatClient _ChatClient;

        public GameObject ChattingMain;

        public Button _CloseChattingMainBtn;

        public TMP_Text _ServerStateText;

        public TMP_InputField _ChatInputField;

        public Button _ChatSendBtn;

        public TMP_Text _ChattingContentText;
        public TMP_Text SubscribeUsersText;

        public ChatChannel _CntChatChannel;

        public GameObject _PlayerSpeaker;

        #region IChatClientListener CallBacks

        public void OnGetMessages(string channelName, string[] senders, object[] messages)
        {
            for (int i = 0; i < senders.Length; i++)
            {
                string sender = senders[i];
                string message = messages[i].ToString();

                string chatMessage = $"[{sender}]: {message}";

                // 채팅 메시지 추가
                _ChattingContentText.text += chatMessage + "\n";
            }
        }

        // 개인 메시지(귓속말) 미구현
        public void OnPrivateMessage(string sender, object message, string channelName)
        {
            Debug.Log("Private message from [" + sender + "]: " + message);
        }

        // 접속한 Room의 이름과 관련된 채널 구독이 성공 CallBack
        public void OnSubscribed(string[] channels, bool[] results)
        {
            _ChattingContentText.text = string.Empty;
            // 채널을 캐싱해준다. (구독 완료된 시점)
            if (_ChatClient.TryGetChannel("Room_" + PhotonNetwork.CurrentRoom.Name, out _CntChatChannel))  

            //// 나가기 버튼 활성화
            //_CloseChattingMainBtn.gameObject.SetActive(true); 

            // 첫 화면 출력
            _ChattingContentText.text += " Welcome to Photon Chat. \n";
            UpdateSubscribeUsersText();
            /* 디버깅
            for (int i = 0; i < channels.Length; i++)
                Debug.Log("Subscribed to Channel: " + channels[i]); 
            */
        }

        // 접속한 Room의 이름과 관련된 채널 구독을 해제 CallBack
        public void OnUnsubscribed(string[] channels)
        {
            // 구독 해제와 동시에 Clear
            _ChattingContentText.text = string.Empty;
            /* 디버깅
            for (int i = 0; i < channels.Length; i++)
                Debug.Log("Unsubscribed from Channel: " + channels[i]);
            */
        }

        // 디버그 CallBack 미구현
        public void DebugReturn(DebugLevel level, string message)
        {
            Debug.Log("Debug: " + level + " - " + message);
        }

        // 디버그 CallBack 미구현
        public void OnDisconnected()
        {
            Debug.Log("Disconnected from Chat Server");
        }

        // 디버그 CallBack 미구현
        public void OnChatStateChange(ChatState state)
        {
            _ServerStateText.text = string.Format($"Server State : {state}");
        }

        // 디버그 CallBack 미구현
        public void OnStatusUpdate(string user, int status, bool gotMessage, object message)
        {
            Debug.Log("Status Update - User: " + user + ", Status: " + status);
        }

        //  어떤 유저가 해당 채널을 구독 CallBack
        public void OnUserSubscribed(string channel, string user)
        {
            // 메시지 창에 어떤 유저가 구독했는지 출력
            string message = $"<color=#06FF00>'{user}' has joined '{channel}'</color>";
            _ChattingContentText.text += message + "\n";
            UpdateSubscribeUsersText();
        }

        //  어떤 유저가 해당 채널을 구독 해제 CallBack
        public void OnUserUnsubscribed(string channel, string user)
        {
            // 메시지 창에 어떤 유저가 구독을 해제했는지 출력
            string message = $"<color=#FF0000> '{user}' has left '{channel}'</color>";
            _ChattingContentText.text += message + "\n";
            UpdateSubscribeUsersText();
        }


        #endregion


        #region MonoBehaviour

        private void Awake()
        {         
            ChattingMain.SetActive(false);
            _CloseChattingMainBtn.onClick.AddListener(() => {
                // 나가기 버튼 비활성화
                _CloseChattingMainBtn.gameObject.SetActive(false);
                PhotonNetwork.LeaveRoom();
            });
            _ChatSendBtn.onClick.AddListener(() => {
                if (_ChatInputField.text == string.Empty) return;
                else {
                    SendRoomMessage(_ChatInputField.text);
                    _ChatInputField.text = string.Empty;
                }
            });
        }

        private void Start()
        {
            // 액션 등록
            NetworkManager.Instance.OnJoinedRoomAction += ChattingOnJoinedRoom;
            NetworkManager.Instance.OnLeftRoomAction += ChattingOnLeftRoom;
            NetworkManager.Instance.OnJoinedLobbyAction += ChattingOnJoinedLobby;
        }

        private void Update()
        {
            // !!필수!!
            // ChatClient의 콜백 처리를 위해 주기적으로 호출
            if (_ChatClient != null)
                _ChatClient.Service();
        }

        #endregion



        #region ActionFuctions

        /// <summary>
        /// 로비에 접속하면 Chatting Server를 활성화 시킨다.
        /// </summary>
        private void ChattingOnJoinedLobby()
        {
            // 구독 전에 나가는 것을 방지
            _CloseChattingMainBtn.gameObject.SetActive(false); 

            if (_ChatClient == null) {
                // 로비에 접속한 후 ChatClient 초기화 및 연결
                _ChatClient = new ChatClient(this);
                _ChatClient.Connect(PhotonNetwork.PhotonServerSettings.AppSettings.AppIdChat, PhotonNetwork.AppVersion, new Photon.Chat.AuthenticationValues(PhotonNetwork.NickName));
                Debug.LogFormat($"Chatting Server 초기화 및 연결 시작");
            }
        }

        /// <summary>
        ///  Room에 접속하면 실행되는 함수
        ///  Room의 이름과 관련된 채팅 서버에 접속한다.
        /// </summary>
        private void ChattingOnJoinedRoom()
        {
            _ChattingContentText.text = string.Empty;
            ChattingMain.SetActive(true);
            string _Channel = "Room_" + PhotonNetwork.CurrentRoom.Name; // 가입할 채널 이름 배열
            _ChatClient.Subscribe(_Channel, 0, -1, new ChannelCreationOptions { PublishSubscribers = true } ); // 채널에 구독
            Debug.LogFormat($"{"Room_" + PhotonNetwork.CurrentRoom.Name} :: 구독");

            _PlayerSpeaker = PhotonNetwork.Instantiate("PlayerVoiceChatPrefab", Vector3.zero, Quaternion.identity);
            _PlayerSpeaker.GetComponent<Speaker>().enabled = false;
            _PlayerSpeaker.GetComponent<AudioSource>().enabled = false;
            // 나가기 버튼 활성화
            _CloseChattingMainBtn.gameObject.SetActive(true);
        }

        /// <summary>
        /// Room에서 나갈 경우 실행되는 함수
        /// </summary>
        private void ChattingOnLeftRoom()
        {
            PhotonNetwork.Destroy(_PlayerSpeaker.GetPhotonView());
            string[] _Channels = new string[] { _CntChatChannel.Name };
            _ChatClient.Unsubscribe(_Channels);
            _CntChatChannel = null;
            ChattingMain.SetActive(false);
        }

        #endregion

        /// <summary>
        /// 구독한 Channel에 메시지를 보냅니다.
        /// </summary>
        /// <param name="message"></param>
        private void SendRoomMessage(string message)
        {
            if (_ChatClient != null && _ChatClient.CanChat) {
                //  _ChatClient가 구독한 방에 메시지를 보냅니다.
                _ChatClient.PublishMessage("Room_" + PhotonNetwork.CurrentRoom.Name, message);
            }
        }

        /// <summary>
        /// 체널의 있는 유저들의 정보를 갱신합니다.
        /// </summary>
        private void UpdateSubscribeUsersText()
        {
            if (_ChatClient != null && _CntChatChannel != null) {
                var _Users = _CntChatChannel.Subscribers;
                SubscribeUsersText.text = string.Empty;
                foreach (var _user in _Users)
                    SubscribeUsersText.text += _user + "  ";               
            }
            else SubscribeUsersText.text = "There is a problem with the network.";
           
        }
    }






}

APK File

  • 마이크 온오프 기능 추가
  • 스피커 관련 오류 수정

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤