ChatGui.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <copyright company="Exit Games GmbH"/>
  3. // <summary>Demo code for Photon Chat in Unity.</summary>
  4. // <author>developer@exitgames.com</author>
  5. // --------------------------------------------------------------------------------------------------------------------
  6. using System;
  7. using System.Collections.Generic;
  8. using UnityEngine;
  9. using UnityEngine.UI;
  10. using Photon.Chat;
  11. using Photon.Realtime;
  12. using AuthenticationValues = Photon.Chat.AuthenticationValues;
  13. #if PHOTON_UNITY_NETWORKING
  14. using Photon.Pun;
  15. #endif
  16. namespace Photon.Chat.Demo
  17. {
  18. /// <summary>
  19. /// This simple Chat UI demonstrate basics usages of the Chat Api
  20. /// </summary>
  21. /// <remarks>
  22. /// The ChatClient basically lets you create any number of channels.
  23. ///
  24. /// some friends are already set in the Chat demo "DemoChat-Scene", 'Joe', 'Jane' and 'Bob', simply log with them so that you can see the status changes in the Interface
  25. ///
  26. /// Workflow:
  27. /// Create ChatClient, Connect to a server with your AppID, Authenticate the user (apply a unique name,)
  28. /// and subscribe to some channels.
  29. /// Subscribe a channel before you publish to that channel!
  30. ///
  31. ///
  32. /// Note:
  33. /// Don't forget to call ChatClient.Service() on Update to keep the Chatclient operational.
  34. /// </remarks>
  35. public class ChatGui : MonoBehaviour, IChatClientListener
  36. {
  37. public string[] ChannelsToJoinOnConnect; // set in inspector. Demo channels to join automatically.
  38. public string[] FriendsList;
  39. public int HistoryLengthToFetch; // set in inspector. Up to a certain degree, previously sent messages can be fetched for context
  40. public string UserName { get; set; }
  41. private string selectedChannelName; // mainly used for GUI/input
  42. public ChatClient chatClient;
  43. #if !PHOTON_UNITY_NETWORKING
  44. [SerializeField]
  45. #endif
  46. protected internal ChatAppSettings chatAppSettings;
  47. public GameObject missingAppIdErrorPanel;
  48. public GameObject ConnectingLabel;
  49. public RectTransform ChatPanel; // set in inspector (to enable/disable panel)
  50. public GameObject UserIdFormPanel;
  51. public InputField InputFieldChat; // set in inspector
  52. public Text CurrentChannelText; // set in inspector
  53. public Toggle ChannelToggleToInstantiate; // set in inspector
  54. public GameObject FriendListUiItemtoInstantiate;
  55. private readonly Dictionary<string, Toggle> channelToggles = new Dictionary<string, Toggle>();
  56. private readonly Dictionary<string,FriendItem> friendListItemLUT = new Dictionary<string, FriendItem>();
  57. public bool ShowState = true;
  58. public GameObject Title;
  59. public Text StateText; // set in inspector
  60. public Text UserIdText; // set in inspector
  61. // private static string WelcomeText = "Welcome to chat. Type \\help to list commands.";
  62. private static string HelpText = "\n -- HELP --\n" +
  63. "To subscribe to channel(s) (channelnames are case sensitive) : \n" +
  64. "\t<color=#E07B00>\\subscribe</color> <color=green><list of channelnames></color>\n" +
  65. "\tor\n" +
  66. "\t<color=#E07B00>\\s</color> <color=green><list of channelnames></color>\n" +
  67. "\n" +
  68. "To leave channel(s):\n" +
  69. "\t<color=#E07B00>\\unsubscribe</color> <color=green><list of channelnames></color>\n" +
  70. "\tor\n" +
  71. "\t<color=#E07B00>\\u</color> <color=green><list of channelnames></color>\n" +
  72. "\n" +
  73. "To switch the active channel\n" +
  74. "\t<color=#E07B00>\\join</color> <color=green><channelname></color>\n" +
  75. "\tor\n" +
  76. "\t<color=#E07B00>\\j</color> <color=green><channelname></color>\n" +
  77. "\n" +
  78. "To send a private message: (username are case sensitive)\n" +
  79. "\t\\<color=#E07B00>msg</color> <color=green><username></color> <color=green><message></color>\n" +
  80. "\n" +
  81. "To change status:\n" +
  82. "\t\\<color=#E07B00>state</color> <color=green><stateIndex></color> <color=green><message></color>\n" +
  83. "<color=green>0</color> = Offline " +
  84. "<color=green>1</color> = Invisible " +
  85. "<color=green>2</color> = Online " +
  86. "<color=green>3</color> = Away \n" +
  87. "<color=green>4</color> = Do not disturb " +
  88. "<color=green>5</color> = Looking For Group " +
  89. "<color=green>6</color> = Playing" +
  90. "\n\n" +
  91. "To clear the current chat tab (private chats get closed):\n" +
  92. "\t<color=#E07B00>\\clear</color>";
  93. public void Start()
  94. {
  95. DontDestroyOnLoad(this.gameObject);
  96. this.UserIdText.text = "";
  97. this.StateText.text = "";
  98. this.StateText.gameObject.SetActive(true);
  99. this.UserIdText.gameObject.SetActive(true);
  100. this.Title.SetActive(true);
  101. this.ChatPanel.gameObject.SetActive(false);
  102. this.ConnectingLabel.SetActive(false);
  103. if (string.IsNullOrEmpty(this.UserName))
  104. {
  105. this.UserName = "user" + Environment.TickCount%99; //made-up username
  106. }
  107. #if PHOTON_UNITY_NETWORKING
  108. this.chatAppSettings = PhotonNetwork.PhotonServerSettings.AppSettings.GetChatSettings();
  109. #endif
  110. bool appIdPresent = !string.IsNullOrEmpty(this.chatAppSettings.AppIdChat);
  111. this.missingAppIdErrorPanel.SetActive(!appIdPresent);
  112. this.UserIdFormPanel.gameObject.SetActive(appIdPresent);
  113. if (!appIdPresent)
  114. {
  115. Debug.LogError("You need to set the chat app ID in the PhotonServerSettings file in order to continue.");
  116. }
  117. }
  118. public void Connect()
  119. {
  120. this.UserIdFormPanel.gameObject.SetActive(false);
  121. this.chatClient = new ChatClient(this);
  122. #if !UNITY_WEBGL
  123. this.chatClient.UseBackgroundWorkerForSending = true;
  124. #endif
  125. this.chatClient.AuthValues = new AuthenticationValues(this.UserName);
  126. this.chatClient.ConnectUsingSettings(this.chatAppSettings);
  127. this.ChannelToggleToInstantiate.gameObject.SetActive(false);
  128. Debug.Log("Connecting as: " + this.UserName);
  129. this.ConnectingLabel.SetActive(true);
  130. }
  131. /// <summary>To avoid that the Editor becomes unresponsive, disconnect all Photon connections in OnDestroy.</summary>
  132. public void OnDestroy()
  133. {
  134. if (this.chatClient != null)
  135. {
  136. this.chatClient.Disconnect();
  137. }
  138. }
  139. /// <summary>To avoid that the Editor becomes unresponsive, disconnect all Photon connections in OnApplicationQuit.</summary>
  140. public void OnApplicationQuit()
  141. {
  142. if (this.chatClient != null)
  143. {
  144. this.chatClient.Disconnect();
  145. }
  146. }
  147. public void Update()
  148. {
  149. if (this.chatClient != null)
  150. {
  151. this.chatClient.Service(); // make sure to call this regularly! it limits effort internally, so calling often is ok!
  152. }
  153. // check if we are missing context, which means we got kicked out to get back to the Photon Demo hub.
  154. if ( this.StateText == null)
  155. {
  156. Destroy(this.gameObject);
  157. return;
  158. }
  159. this.StateText.gameObject.SetActive(this.ShowState); // this could be handled more elegantly, but for the demo it's ok.
  160. }
  161. public void OnEnterSend()
  162. {
  163. if (Input.GetKey(KeyCode.Return) || Input.GetKey(KeyCode.KeypadEnter))
  164. {
  165. this.SendChatMessage(this.InputFieldChat.text);
  166. this.InputFieldChat.text = "";
  167. }
  168. }
  169. public void OnClickSend()
  170. {
  171. if (this.InputFieldChat != null)
  172. {
  173. this.SendChatMessage(this.InputFieldChat.text);
  174. this.InputFieldChat.text = "";
  175. }
  176. }
  177. public int TestLength = 2048;
  178. private byte[] testBytes = new byte[2048];
  179. private void SendChatMessage(string inputLine)
  180. {
  181. if (string.IsNullOrEmpty(inputLine))
  182. {
  183. return;
  184. }
  185. if ("test".Equals(inputLine))
  186. {
  187. if (this.TestLength != this.testBytes.Length)
  188. {
  189. this.testBytes = new byte[this.TestLength];
  190. }
  191. this.chatClient.SendPrivateMessage(this.chatClient.AuthValues.UserId, this.testBytes, true);
  192. }
  193. bool doingPrivateChat = this.chatClient.PrivateChannels.ContainsKey(this.selectedChannelName);
  194. string privateChatTarget = string.Empty;
  195. if (doingPrivateChat)
  196. {
  197. // the channel name for a private conversation is (on the client!!) always composed of both user's IDs: "this:remote"
  198. // so the remote ID is simple to figure out
  199. string[] splitNames = this.selectedChannelName.Split(new char[] { ':' });
  200. privateChatTarget = splitNames[1];
  201. }
  202. //UnityEngine.Debug.Log("selectedChannelName: " + selectedChannelName + " doingPrivateChat: " + doingPrivateChat + " privateChatTarget: " + privateChatTarget);
  203. if (inputLine[0].Equals('\\'))
  204. {
  205. string[] tokens = inputLine.Split(new char[] {' '}, 2);
  206. if (tokens[0].Equals("\\help"))
  207. {
  208. this.PostHelpToCurrentChannel();
  209. }
  210. if (tokens[0].Equals("\\state"))
  211. {
  212. int newState = 0;
  213. List<string> messages = new List<string>();
  214. messages.Add ("i am state " + newState);
  215. string[] subtokens = tokens[1].Split(new char[] {' ', ','});
  216. if (subtokens.Length > 0)
  217. {
  218. newState = int.Parse(subtokens[0]);
  219. }
  220. if (subtokens.Length > 1)
  221. {
  222. messages.Add(subtokens[1]);
  223. }
  224. this.chatClient.SetOnlineStatus(newState,messages.ToArray()); // this is how you set your own state and (any) message
  225. }
  226. else if ((tokens[0].Equals("\\subscribe") || tokens[0].Equals("\\s")) && !string.IsNullOrEmpty(tokens[1]))
  227. {
  228. this.chatClient.Subscribe(tokens[1].Split(new char[] {' ', ','}));
  229. }
  230. else if ((tokens[0].Equals("\\unsubscribe") || tokens[0].Equals("\\u")) && !string.IsNullOrEmpty(tokens[1]))
  231. {
  232. this.chatClient.Unsubscribe(tokens[1].Split(new char[] {' ', ','}));
  233. }
  234. else if (tokens[0].Equals("\\clear"))
  235. {
  236. if (doingPrivateChat)
  237. {
  238. this.chatClient.PrivateChannels.Remove(this.selectedChannelName);
  239. }
  240. else
  241. {
  242. ChatChannel channel;
  243. if (this.chatClient.TryGetChannel(this.selectedChannelName, doingPrivateChat, out channel))
  244. {
  245. channel.ClearMessages();
  246. }
  247. }
  248. }
  249. else if (tokens[0].Equals("\\msg") && !string.IsNullOrEmpty(tokens[1]))
  250. {
  251. string[] subtokens = tokens[1].Split(new char[] {' ', ','}, 2);
  252. if (subtokens.Length < 2) return;
  253. string targetUser = subtokens[0];
  254. string message = subtokens[1];
  255. this.chatClient.SendPrivateMessage(targetUser, message);
  256. }
  257. else if ((tokens[0].Equals("\\join") || tokens[0].Equals("\\j")) && !string.IsNullOrEmpty(tokens[1]))
  258. {
  259. string[] subtokens = tokens[1].Split(new char[] { ' ', ',' }, 2);
  260. // If we are already subscribed to the channel we directly switch to it, otherwise we subscribe to it first and then switch to it implicitly
  261. if (this.channelToggles.ContainsKey(subtokens[0]))
  262. {
  263. this.ShowChannel(subtokens[0]);
  264. }
  265. else
  266. {
  267. this.chatClient.Subscribe(new string[] { subtokens[0] });
  268. }
  269. }
  270. else
  271. {
  272. Debug.Log("The command '" + tokens[0] + "' is invalid.");
  273. }
  274. }
  275. else
  276. {
  277. if (doingPrivateChat)
  278. {
  279. this.chatClient.SendPrivateMessage(privateChatTarget, inputLine);
  280. }
  281. else
  282. {
  283. this.chatClient.PublishMessage(this.selectedChannelName, inputLine);
  284. }
  285. }
  286. }
  287. public void PostHelpToCurrentChannel()
  288. {
  289. this.CurrentChannelText.text += HelpText;
  290. }
  291. public void DebugReturn(ExitGames.Client.Photon.DebugLevel level, string message)
  292. {
  293. if (level == ExitGames.Client.Photon.DebugLevel.ERROR)
  294. {
  295. Debug.LogError(message);
  296. }
  297. else if (level == ExitGames.Client.Photon.DebugLevel.WARNING)
  298. {
  299. Debug.LogWarning(message);
  300. }
  301. else
  302. {
  303. Debug.Log(message);
  304. }
  305. }
  306. public void OnConnected()
  307. {
  308. if (this.ChannelsToJoinOnConnect != null && this.ChannelsToJoinOnConnect.Length > 0)
  309. {
  310. this.chatClient.Subscribe(this.ChannelsToJoinOnConnect, this.HistoryLengthToFetch);
  311. }
  312. this.ConnectingLabel.SetActive(false);
  313. this.UserIdText.text = "Connected as "+ this.UserName;
  314. this.ChatPanel.gameObject.SetActive(true);
  315. if (this.FriendsList!=null && this.FriendsList.Length>0)
  316. {
  317. this.chatClient.AddFriends(this.FriendsList); // Add some users to the server-list to get their status updates
  318. // add to the UI as well
  319. foreach(string _friend in this.FriendsList)
  320. {
  321. if (this.FriendListUiItemtoInstantiate != null && _friend!= this.UserName)
  322. {
  323. this.InstantiateFriendButton(_friend);
  324. }
  325. }
  326. }
  327. if (this.FriendListUiItemtoInstantiate != null)
  328. {
  329. this.FriendListUiItemtoInstantiate.SetActive(false);
  330. }
  331. this.chatClient.SetOnlineStatus(ChatUserStatus.Online); // You can set your online state (without a mesage).
  332. }
  333. public void OnDisconnected()
  334. {
  335. this.ConnectingLabel.SetActive(false);
  336. }
  337. public void OnChatStateChange(ChatState state)
  338. {
  339. // use OnConnected() and OnDisconnected()
  340. // this method might become more useful in the future, when more complex states are being used.
  341. this.StateText.text = state.ToString();
  342. }
  343. public void OnSubscribed(string[] channels, bool[] results)
  344. {
  345. // in this demo, we simply send a message into each channel. This is NOT a must have!
  346. foreach (string channel in channels)
  347. {
  348. this.chatClient.PublishMessage(channel, "says 'hi'."); // you don't HAVE to send a msg on join but you could.
  349. if (this.ChannelToggleToInstantiate != null)
  350. {
  351. this.InstantiateChannelButton(channel);
  352. }
  353. }
  354. Debug.Log("OnSubscribed: " + string.Join(", ", channels));
  355. /*
  356. // select first subscribed channel in alphabetical order
  357. if (this.chatClient.PublicChannels.Count > 0)
  358. {
  359. var l = new List<string>(this.chatClient.PublicChannels.Keys);
  360. l.Sort();
  361. string selected = l[0];
  362. if (this.channelToggles.ContainsKey(selected))
  363. {
  364. ShowChannel(selected);
  365. foreach (var c in this.channelToggles)
  366. {
  367. c.Value.isOn = false;
  368. }
  369. this.channelToggles[selected].isOn = true;
  370. AddMessageToSelectedChannel(WelcomeText);
  371. }
  372. }
  373. */
  374. // Switch to the first newly created channel
  375. this.ShowChannel(channels[0]);
  376. }
  377. /// <inheritdoc />
  378. public void OnSubscribed(string channel, string[] users, Dictionary<object, object> properties)
  379. {
  380. Debug.LogFormat("OnSubscribed: {0}, users.Count: {1} Channel-props: {2}.", channel, users.Length, properties.ToStringFull());
  381. }
  382. private void InstantiateChannelButton(string channelName)
  383. {
  384. if (this.channelToggles.ContainsKey(channelName))
  385. {
  386. Debug.Log("Skipping creation for an existing channel toggle.");
  387. return;
  388. }
  389. Toggle cbtn = (Toggle)Instantiate(this.ChannelToggleToInstantiate);
  390. cbtn.gameObject.SetActive(true);
  391. cbtn.GetComponentInChildren<ChannelSelector>().SetChannel(channelName);
  392. cbtn.transform.SetParent(this.ChannelToggleToInstantiate.transform.parent, false);
  393. this.channelToggles.Add(channelName, cbtn);
  394. }
  395. private void InstantiateFriendButton(string friendId)
  396. {
  397. GameObject fbtn = (GameObject)Instantiate(this.FriendListUiItemtoInstantiate);
  398. fbtn.gameObject.SetActive(true);
  399. FriendItem _friendItem = fbtn.GetComponent<FriendItem>();
  400. _friendItem.FriendId = friendId;
  401. fbtn.transform.SetParent(this.FriendListUiItemtoInstantiate.transform.parent, false);
  402. this.friendListItemLUT[friendId] = _friendItem;
  403. }
  404. public void OnUnsubscribed(string[] channels)
  405. {
  406. foreach (string channelName in channels)
  407. {
  408. if (this.channelToggles.ContainsKey(channelName))
  409. {
  410. Toggle t = this.channelToggles[channelName];
  411. Destroy(t.gameObject);
  412. this.channelToggles.Remove(channelName);
  413. Debug.Log("Unsubscribed from channel '" + channelName + "'.");
  414. // Showing another channel if the active channel is the one we unsubscribed from before
  415. if (channelName == this.selectedChannelName && this.channelToggles.Count > 0)
  416. {
  417. IEnumerator<KeyValuePair<string, Toggle>> firstEntry = this.channelToggles.GetEnumerator();
  418. firstEntry.MoveNext();
  419. this.ShowChannel(firstEntry.Current.Key);
  420. firstEntry.Current.Value.isOn = true;
  421. }
  422. }
  423. else
  424. {
  425. Debug.Log("Can't unsubscribe from channel '" + channelName + "' because you are currently not subscribed to it.");
  426. }
  427. }
  428. }
  429. public void OnGetMessages(string channelName, string[] senders, object[] messages)
  430. {
  431. if (channelName.Equals(this.selectedChannelName))
  432. {
  433. // update text
  434. this.ShowChannel(this.selectedChannelName);
  435. }
  436. }
  437. public void OnPrivateMessage(string sender, object message, string channelName)
  438. {
  439. // as the ChatClient is buffering the messages for you, this GUI doesn't need to do anything here
  440. // you also get messages that you sent yourself. in that case, the channelName is determinded by the target of your msg
  441. this.InstantiateChannelButton(channelName);
  442. byte[] msgBytes = message as byte[];
  443. if (msgBytes != null)
  444. {
  445. Debug.Log("Message with byte[].Length: "+ msgBytes.Length);
  446. }
  447. if (this.selectedChannelName.Equals(channelName))
  448. {
  449. this.ShowChannel(channelName);
  450. }
  451. }
  452. /// <summary>
  453. /// New status of another user (you get updates for users set in your friends list).
  454. /// </summary>
  455. /// <param name="user">Name of the user.</param>
  456. /// <param name="status">New status of that user.</param>
  457. /// <param name="gotMessage">True if the status contains a message you should cache locally. False: This status update does not include a
  458. /// message (keep any you have).</param>
  459. /// <param name="message">Message that user set.</param>
  460. public void OnStatusUpdate(string user, int status, bool gotMessage, object message)
  461. {
  462. Debug.LogWarning("status: " + string.Format("{0} is {1}. Msg:{2}", user, status, message));
  463. if (this.friendListItemLUT.ContainsKey(user))
  464. {
  465. FriendItem _friendItem = this.friendListItemLUT[user];
  466. if ( _friendItem!=null) _friendItem.OnFriendStatusUpdate(status,gotMessage,message);
  467. }
  468. }
  469. public void OnUserSubscribed(string channel, string user)
  470. {
  471. Debug.LogFormat("OnUserSubscribed: channel=\"{0}\" userId=\"{1}\"", channel, user);
  472. }
  473. public void OnUserUnsubscribed(string channel, string user)
  474. {
  475. Debug.LogFormat("OnUserUnsubscribed: channel=\"{0}\" userId=\"{1}\"", channel, user);
  476. }
  477. /// <inheritdoc />
  478. public void OnChannelPropertiesChanged(string channel, string userId, Dictionary<object, object> properties)
  479. {
  480. Debug.LogFormat("OnChannelPropertiesChanged: {0} by {1}. Props: {2}.", channel, userId, Extensions.ToStringFull(properties));
  481. }
  482. public void OnUserPropertiesChanged(string channel, string targetUserId, string senderUserId, Dictionary<object, object> properties)
  483. {
  484. Debug.LogFormat("OnUserPropertiesChanged: (channel:{0} user:{1}) by {2}. Props: {3}.", channel, targetUserId, senderUserId, Extensions.ToStringFull(properties));
  485. }
  486. /// <inheritdoc />
  487. public void OnErrorInfo(string channel, string error, object data)
  488. {
  489. Debug.LogFormat("OnErrorInfo for channel {0}. Error: {1} Data: {2}", channel, error, data);
  490. }
  491. public void AddMessageToSelectedChannel(string msg)
  492. {
  493. ChatChannel channel = null;
  494. bool found = this.chatClient.TryGetChannel(this.selectedChannelName, out channel);
  495. if (!found)
  496. {
  497. Debug.Log("AddMessageToSelectedChannel failed to find channel: " + this.selectedChannelName);
  498. return;
  499. }
  500. if (channel != null)
  501. {
  502. channel.Add("Bot", msg,0); //TODO: how to use msgID?
  503. }
  504. }
  505. public void ShowChannel(string channelName)
  506. {
  507. if (string.IsNullOrEmpty(channelName))
  508. {
  509. return;
  510. }
  511. ChatChannel channel = null;
  512. bool found = this.chatClient.TryGetChannel(channelName, out channel);
  513. if (!found)
  514. {
  515. Debug.Log("ShowChannel failed to find channel: " + channelName);
  516. return;
  517. }
  518. this.selectedChannelName = channelName;
  519. this.CurrentChannelText.text = channel.ToStringMessages();
  520. Debug.Log("ShowChannel: " + this.selectedChannelName);
  521. foreach (KeyValuePair<string, Toggle> pair in this.channelToggles)
  522. {
  523. pair.Value.isOn = pair.Key == channelName ? true : false;
  524. }
  525. }
  526. public void OpenDashboard()
  527. {
  528. Application.OpenURL("https://dashboard.photonengine.com");
  529. }
  530. }
  531. }