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
- 마이크 온오프 기능 추가
- 스피커 관련 오류 수정


