PlayerNumbering.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <copyright file="PlayerNumbering.cs" company="Exit Games GmbH">
  3. // Part of: Photon Unity Utilities,
  4. // </copyright>
  5. // <summary>
  6. // Assign numbers to Players in a room. Uses Room custom Properties
  7. // </summary>
  8. // <author>developer@exitgames.com</author>
  9. // --------------------------------------------------------------------------------------------------------------------
  10. using System.Collections.Generic;
  11. using System.Linq;
  12. using UnityEngine;
  13. using Photon.Pun;
  14. using Photon.Realtime;
  15. using Hashtable = ExitGames.Client.Photon.Hashtable;
  16. namespace Photon.Pun.UtilityScripts
  17. {
  18. /// <summary>
  19. /// Implements consistent numbering in a room/game with help of room properties. Access them by Player.GetPlayerNumber() extension.
  20. /// </summary>
  21. /// <remarks>
  22. /// indexing ranges from 0 to the maximum number of Players.
  23. /// indexing remains for the player while in room.
  24. /// If a Player is numbered 2 and player numbered 1 leaves, numbered 1 become vacant and will assigned to the future player joining (the first available vacant number is assigned when joining)
  25. /// </remarks>
  26. public class PlayerNumbering : MonoBehaviourPunCallbacks
  27. {
  28. //TODO: Add a "numbers available" bool, to allow easy access to this?!
  29. #region Public Properties
  30. /// <summary>
  31. /// The instance. EntryPoint to query about Room Indexing.
  32. /// </summary>
  33. public static PlayerNumbering instance;
  34. public static Player[] SortedPlayers;
  35. /// <summary>
  36. /// OnPlayerNumberingChanged delegate. Use
  37. /// </summary>
  38. public delegate void PlayerNumberingChanged();
  39. /// <summary>
  40. /// Called everytime the room Indexing was updated. Use this for discrete updates. Always better than brute force calls every frame.
  41. /// </summary>
  42. public static event PlayerNumberingChanged OnPlayerNumberingChanged;
  43. /// <summary>Defines the room custom property name to use for room player indexing tracking.</summary>
  44. public const string RoomPlayerIndexedProp = "pNr";
  45. /// <summary>
  46. /// dont destroy on load flag for this Component's GameObject to survive Level Loading.
  47. /// </summary>
  48. public bool dontDestroyOnLoad = false;
  49. #endregion
  50. #region MonoBehaviours methods
  51. public void Awake()
  52. {
  53. if (instance != null && instance != this && instance.gameObject != null)
  54. {
  55. GameObject.DestroyImmediate(instance.gameObject);
  56. }
  57. instance = this;
  58. if (dontDestroyOnLoad)
  59. {
  60. DontDestroyOnLoad(this.gameObject);
  61. }
  62. this.RefreshData();
  63. }
  64. #endregion
  65. #region PunBehavior Overrides
  66. public override void OnJoinedRoom()
  67. {
  68. this.RefreshData();
  69. }
  70. public override void OnLeftRoom()
  71. {
  72. PhotonNetwork.LocalPlayer.CustomProperties.Remove(PlayerNumbering.RoomPlayerIndexedProp);
  73. }
  74. public override void OnPlayerEnteredRoom(Player newPlayer)
  75. {
  76. this.RefreshData();
  77. }
  78. public override void OnPlayerLeftRoom(Player otherPlayer)
  79. {
  80. this.RefreshData();
  81. }
  82. public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
  83. {
  84. if (changedProps != null && changedProps.ContainsKey(PlayerNumbering.RoomPlayerIndexedProp))
  85. {
  86. this.RefreshData();
  87. }
  88. }
  89. #endregion
  90. // each player can select it's own playernumber in a room, if all "older" players already selected theirs
  91. /// <summary>
  92. /// Internal call Refresh the cached data and call the OnPlayerNumberingChanged delegate.
  93. /// </summary>
  94. public void RefreshData()
  95. {
  96. if (PhotonNetwork.CurrentRoom == null)
  97. {
  98. return;
  99. }
  100. if (PhotonNetwork.LocalPlayer.GetPlayerNumber() >= 0)
  101. {
  102. SortedPlayers = PhotonNetwork.CurrentRoom.Players.Values.OrderBy((p) => p.GetPlayerNumber()).ToArray();
  103. if (OnPlayerNumberingChanged != null)
  104. {
  105. OnPlayerNumberingChanged();
  106. }
  107. return;
  108. }
  109. HashSet<int> usedInts = new HashSet<int>();
  110. Player[] sorted = PhotonNetwork.PlayerList.OrderBy((p) => p.ActorNumber).ToArray();
  111. string allPlayers = "all players: ";
  112. foreach (Player player in sorted)
  113. {
  114. allPlayers += player.ActorNumber + "=pNr:"+player.GetPlayerNumber()+", ";
  115. int number = player.GetPlayerNumber();
  116. // if it's this user, select a number and break
  117. // else:
  118. // check if that user has a number
  119. // if not, break!
  120. // else remember used numbers
  121. if (player.IsLocal)
  122. {
  123. Debug.Log ("PhotonNetwork.CurrentRoom.PlayerCount = " + PhotonNetwork.CurrentRoom.PlayerCount);
  124. // select a number
  125. for (int i = 0; i < PhotonNetwork.CurrentRoom.PlayerCount; i++)
  126. {
  127. if (!usedInts.Contains(i))
  128. {
  129. player.SetPlayerNumber(i);
  130. break;
  131. }
  132. }
  133. // then break
  134. break;
  135. }
  136. else
  137. {
  138. if (number < 0)
  139. {
  140. break;
  141. }
  142. else
  143. {
  144. usedInts.Add(number);
  145. }
  146. }
  147. }
  148. //Debug.Log(allPlayers);
  149. //Debug.Log(PhotonNetwork.LocalPlayer.ToStringFull() + " has PhotonNetwork.player.GetPlayerNumber(): " + PhotonNetwork.LocalPlayer.GetPlayerNumber());
  150. SortedPlayers = PhotonNetwork.CurrentRoom.Players.Values.OrderBy((p) => p.GetPlayerNumber()).ToArray();
  151. if (OnPlayerNumberingChanged != null)
  152. {
  153. OnPlayerNumberingChanged();
  154. }
  155. }
  156. }
  157. /// <summary>Extension used for PlayerRoomIndexing and Player class.</summary>
  158. public static class PlayerNumberingExtensions
  159. {
  160. /// <summary>Extension for Player class to wrap up access to the player's custom property.
  161. /// Make sure you use the delegate 'OnPlayerNumberingChanged' to knoiw when you can query the PlayerNumber. Numbering can changes over time or not be yet assigned during the initial phase ( when player creates a room for example)
  162. /// </summary>
  163. /// <returns>persistent index in room. -1 for no indexing</returns>
  164. public static int GetPlayerNumber(this Player player)
  165. {
  166. if (player == null) {
  167. return -1;
  168. }
  169. if (PhotonNetwork.OfflineMode)
  170. {
  171. return 0;
  172. }
  173. if (!PhotonNetwork.IsConnectedAndReady)
  174. {
  175. return -1;
  176. }
  177. object value;
  178. if (player.CustomProperties.TryGetValue (PlayerNumbering.RoomPlayerIndexedProp, out value)) {
  179. return (byte)value;
  180. }
  181. return -1;
  182. }
  183. /// <summary>
  184. /// Sets the player number.
  185. /// It's not recommanded to manually interfere with the playerNumbering, but possible.
  186. /// </summary>
  187. /// <param name="player">Player.</param>
  188. /// <param name="playerNumber">Player number.</param>
  189. public static void SetPlayerNumber(this Player player, int playerNumber)
  190. {
  191. if (player == null) {
  192. return;
  193. }
  194. if (PhotonNetwork.OfflineMode)
  195. {
  196. return;
  197. }
  198. if (playerNumber < 0)
  199. {
  200. Debug.LogWarning("Setting invalid playerNumber: " + playerNumber + " for: " + player.ToStringFull());
  201. }
  202. if (!PhotonNetwork.IsConnectedAndReady)
  203. {
  204. Debug.LogWarning("SetPlayerNumber was called in state: " + PhotonNetwork.NetworkClientState + ". Not IsConnectedAndReady.");
  205. return;
  206. }
  207. int current = player.GetPlayerNumber();
  208. if (current != playerNumber)
  209. {
  210. Debug.Log("PlayerNumbering: Set number "+playerNumber);
  211. player.SetCustomProperties(new Hashtable() { { PlayerNumbering.RoomPlayerIndexedProp, (byte)playerNumber } });
  212. }
  213. }
  214. }
  215. }