OnJoinedInstantiate.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <copyright file="OnJoinedInstantiate.cs" company="Exit Games GmbH">
  3. // Part of: Photon Unity Utilities,
  4. // </copyright>
  5. // <summary>
  6. // This component will instantiate a network GameObject when a room is joined
  7. // </summary>
  8. // <author>developer@exitgames.com</author>
  9. // --------------------------------------------------------------------------------------------------------------------
  10. using System.Collections.Generic;
  11. using UnityEngine;
  12. using UnityEngine.Serialization;
  13. using Photon.Realtime;
  14. #if UNITY_EDITOR
  15. using UnityEditor;
  16. #endif
  17. namespace Photon.Pun.UtilityScripts
  18. {
  19. /// <summary>
  20. /// This component will instantiate a network GameObject when a room is joined
  21. /// </summary>
  22. public class OnJoinedInstantiate : MonoBehaviour
  23. , IMatchmakingCallbacks
  24. {
  25. public enum SpawnSequence { Connection, Random, RoundRobin }
  26. #region Inspector Items
  27. // Old field, only here for backwards compat. Value copies over to SpawnPoints in OnValidate
  28. [HideInInspector] private Transform SpawnPosition;
  29. [HideInInspector] public SpawnSequence Sequence = SpawnSequence.Connection;
  30. [HideInInspector] public List<Transform> SpawnPoints = new List<Transform>(1) { null };
  31. [Tooltip("Add a random variance to a spawn point position. GetRandomOffset() can be overridden with your own method for producing offsets.")]
  32. [HideInInspector] public bool UseRandomOffset = true;
  33. [Tooltip("Radius of the RandomOffset.")]
  34. [FormerlySerializedAs("PositionOffset")]
  35. [HideInInspector] public float RandomOffset = 2.0f;
  36. [Tooltip("Disables the Y axis of RandomOffset. The Y value of the spawn point will be used.")]
  37. [HideInInspector] public bool ClampY = true;
  38. [HideInInspector] public List<GameObject> PrefabsToInstantiate = new List<GameObject>(1) { null }; // set in inspector
  39. [FormerlySerializedAs("autoSpawnObjects")]
  40. [HideInInspector] public bool AutoSpawnObjects = true;
  41. #endregion
  42. // Record of spawned objects, used for Despawn All
  43. public Stack<GameObject> SpawnedObjects = new Stack<GameObject>();
  44. protected int spawnedAsActorId;
  45. #if UNITY_EDITOR
  46. protected void OnValidate()
  47. {
  48. /// Check the prefab to make sure it is the actual resource, and not a scene object or other instance.
  49. if (PrefabsToInstantiate != null)
  50. for (int i = 0; i < PrefabsToInstantiate.Count; ++i)
  51. {
  52. var prefab = PrefabsToInstantiate[i];
  53. if (prefab)
  54. PrefabsToInstantiate[i] = ValidatePrefab(prefab);
  55. }
  56. /// Move any values from old SpawnPosition field to new SpawnPoints
  57. if (SpawnPosition)
  58. {
  59. if (SpawnPoints == null)
  60. SpawnPoints = new List<Transform>();
  61. SpawnPoints.Add(SpawnPosition);
  62. SpawnPosition = null;
  63. }
  64. }
  65. /// <summary>
  66. /// Validate, and if valid add this prefab to the first null element of the list, or create a new element. Returns true if the object was added.
  67. /// </summary>
  68. /// <param name="prefab"></param>
  69. public bool AddPrefabToList(GameObject prefab)
  70. {
  71. var validated = ValidatePrefab(prefab);
  72. if (validated)
  73. {
  74. // Don't add to list if this prefab already is on the list
  75. if (PrefabsToInstantiate.Contains(validated))
  76. return false;
  77. // First try to use any null array slots to keep things tidy
  78. if (PrefabsToInstantiate.Contains(null))
  79. PrefabsToInstantiate[PrefabsToInstantiate.IndexOf(null)] = validated;
  80. // Otherwise, just add this prefab.
  81. else
  82. PrefabsToInstantiate.Add(validated);
  83. return true;
  84. }
  85. return false;
  86. }
  87. /// <summary>
  88. /// Determines if the supplied GameObject is an instance of a prefab, or the actual source Asset,
  89. /// and returns a best guess at the actual resource the dev intended to use.
  90. /// </summary>
  91. /// <returns></returns>
  92. protected static GameObject ValidatePrefab(GameObject unvalidated)
  93. {
  94. if (unvalidated == null)
  95. return null;
  96. if (!unvalidated.GetComponent<PhotonView>())
  97. return null;
  98. #if UNITY_2018_3_OR_NEWER
  99. GameObject validated = null;
  100. if (unvalidated != null)
  101. {
  102. if (PrefabUtility.IsPartOfPrefabAsset(unvalidated))
  103. return unvalidated;
  104. var prefabStatus = PrefabUtility.GetPrefabInstanceStatus(unvalidated);
  105. var isValidPrefab = prefabStatus == PrefabInstanceStatus.Connected || prefabStatus == PrefabInstanceStatus.Disconnected;
  106. if (isValidPrefab)
  107. validated = PrefabUtility.GetCorrespondingObjectFromSource(unvalidated) as GameObject;
  108. else
  109. return null;
  110. if (!PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(validated).Contains("/Resources"))
  111. Debug.LogWarning("Player Prefab needs to be a Prefab in a Resource folder.");
  112. }
  113. #else
  114. GameObject validated = unvalidated;
  115. if (unvalidated != null && PrefabUtility.GetPrefabType(unvalidated) != PrefabType.Prefab)
  116. validated = PrefabUtility.GetPrefabParent(unvalidated) as GameObject;
  117. #endif
  118. return validated;
  119. }
  120. #endif
  121. public virtual void OnEnable()
  122. {
  123. PhotonNetwork.AddCallbackTarget(this);
  124. }
  125. public virtual void OnDisable()
  126. {
  127. PhotonNetwork.RemoveCallbackTarget(this);
  128. }
  129. public virtual void OnJoinedRoom()
  130. {
  131. // Only AutoSpawn if we are a new ActorId. Rejoining should reproduce the objects by server instantiation.
  132. if (AutoSpawnObjects && !PhotonNetwork.LocalPlayer.HasRejoined)
  133. {
  134. SpawnObjects();
  135. }
  136. }
  137. public virtual void SpawnObjects()
  138. {
  139. if (this.PrefabsToInstantiate != null)
  140. {
  141. foreach (GameObject o in this.PrefabsToInstantiate)
  142. {
  143. if (o == null)
  144. continue;
  145. #if UNITY_EDITOR
  146. Debug.Log("Auto-Instantiating: " + o.name);
  147. #endif
  148. Vector3 spawnPos; Quaternion spawnRot;
  149. GetSpawnPoint(out spawnPos, out spawnRot);
  150. var newobj = PhotonNetwork.Instantiate(o.name, spawnPos, spawnRot, 0);
  151. SpawnedObjects.Push(newobj);
  152. }
  153. }
  154. }
  155. /// <summary>
  156. /// Destroy all objects that have been spawned by this component for this client.
  157. /// </summary>
  158. /// <param name="localOnly">Use Object.Destroy rather than PhotonNetwork.Destroy.</param>
  159. public virtual void DespawnObjects(bool localOnly)
  160. {
  161. while (SpawnedObjects.Count > 0)
  162. {
  163. var go = SpawnedObjects.Pop();
  164. if (go)
  165. {
  166. if (localOnly)
  167. Object.Destroy(go);
  168. else
  169. PhotonNetwork.Destroy(go);
  170. }
  171. }
  172. }
  173. public virtual void OnFriendListUpdate(List<FriendInfo> friendList) { }
  174. public virtual void OnCreatedRoom() { }
  175. public virtual void OnCreateRoomFailed(short returnCode, string message) { }
  176. public virtual void OnJoinRoomFailed(short returnCode, string message) { }
  177. public virtual void OnJoinRandomFailed(short returnCode, string message) { }
  178. public virtual void OnLeftRoom() { }
  179. protected int lastUsedSpawnPointIndex = -1;
  180. /// <summary>
  181. /// Gets the next SpawnPoint from the list using the SpawnSequence, and applies RandomOffset (if used) to the transform matrix.
  182. /// Override this method with any custom code for coming up with a spawn location. This method is used by AutoSpawn.
  183. /// </summary>
  184. public virtual void GetSpawnPoint(out Vector3 spawnPos, out Quaternion spawnRot)
  185. {
  186. // Fetch a point using the Sequence method indicated
  187. Transform point = GetSpawnPoint();
  188. if (point != null)
  189. {
  190. spawnPos = point.position;
  191. spawnRot = point.rotation;
  192. }
  193. else
  194. {
  195. spawnPos = new Vector3(0, 0, 0);
  196. spawnRot = new Quaternion(0, 0, 0, 1);
  197. }
  198. if (UseRandomOffset)
  199. {
  200. Random.InitState((int)(Time.time * 10000));
  201. spawnPos += GetRandomOffset();
  202. }
  203. }
  204. /// <summary>
  205. /// Get the transform of the next SpawnPoint from the list, selected using the SpawnSequence setting.
  206. /// RandomOffset is not applied, only the transform of the SpawnPoint is returned.
  207. /// Override this method to change how Spawn Point transform is selected. Return the transform you want to use as a spawn point.
  208. /// </summary>
  209. /// <returns></returns>
  210. protected virtual Transform GetSpawnPoint()
  211. {
  212. // Fetch a point using the Sequence method indicated
  213. if (SpawnPoints == null || SpawnPoints.Count == 0)
  214. {
  215. return null;
  216. }
  217. else
  218. {
  219. switch (Sequence)
  220. {
  221. case SpawnSequence.Connection:
  222. {
  223. int id = PhotonNetwork.LocalPlayer.ActorNumber;
  224. return SpawnPoints[(id == -1) ? 0 : id % SpawnPoints.Count];
  225. }
  226. case SpawnSequence.RoundRobin:
  227. {
  228. lastUsedSpawnPointIndex++;
  229. if (lastUsedSpawnPointIndex >= SpawnPoints.Count)
  230. lastUsedSpawnPointIndex = 0;
  231. /// Use Vector.Zero and Quaternion.Identity if we are dealing with no or a null spawnpoint.
  232. return SpawnPoints == null || SpawnPoints.Count == 0 ? null : SpawnPoints[lastUsedSpawnPointIndex];
  233. }
  234. case SpawnSequence.Random:
  235. {
  236. return SpawnPoints[Random.Range(0, SpawnPoints.Count)];
  237. }
  238. default:
  239. return null;
  240. }
  241. }
  242. }
  243. /// <summary>
  244. /// When UseRandomeOffset is enabled, this method is called to produce a Vector3 offset. The default implementation clamps the Y value to zero. You may override this with your own implementation.
  245. /// </summary>
  246. protected virtual Vector3 GetRandomOffset()
  247. {
  248. Vector3 random = Random.insideUnitSphere;
  249. if (ClampY)
  250. random.y = 0;
  251. return RandomOffset * random.normalized;
  252. }
  253. }
  254. #if UNITY_EDITOR
  255. [CustomEditor(typeof(OnJoinedInstantiate), true)]
  256. [CanEditMultipleObjects]
  257. public class OnJoinedInstantiateEditor : Editor
  258. {
  259. SerializedProperty SpawnPoints, PrefabsToInstantiate, UseRandomOffset, ClampY, RandomOffset, Sequence, autoSpawnObjects;
  260. GUIStyle fieldBox;
  261. private void OnEnable()
  262. {
  263. SpawnPoints = serializedObject.FindProperty("SpawnPoints");
  264. PrefabsToInstantiate = serializedObject.FindProperty("PrefabsToInstantiate");
  265. UseRandomOffset = serializedObject.FindProperty("UseRandomOffset");
  266. ClampY = serializedObject.FindProperty("ClampY");
  267. RandomOffset = serializedObject.FindProperty("RandomOffset");
  268. Sequence = serializedObject.FindProperty("Sequence");
  269. autoSpawnObjects = serializedObject.FindProperty("AutoSpawnObjects");
  270. }
  271. public override void OnInspectorGUI()
  272. {
  273. base.OnInspectorGUI();
  274. const int PAD = 6;
  275. if (fieldBox == null)
  276. fieldBox = new GUIStyle("HelpBox") { padding = new RectOffset(PAD, PAD, PAD, PAD) };
  277. EditorGUI.BeginChangeCheck();
  278. EditableReferenceList(PrefabsToInstantiate, new GUIContent(PrefabsToInstantiate.displayName, PrefabsToInstantiate.tooltip), fieldBox);
  279. EditableReferenceList(SpawnPoints, new GUIContent(SpawnPoints.displayName, SpawnPoints.tooltip), fieldBox);
  280. /// Spawn Pattern
  281. EditorGUILayout.BeginVertical(fieldBox);
  282. EditorGUILayout.PropertyField(Sequence);
  283. EditorGUILayout.PropertyField(UseRandomOffset);
  284. if (UseRandomOffset.boolValue)
  285. {
  286. EditorGUILayout.PropertyField(RandomOffset);
  287. EditorGUILayout.PropertyField(ClampY);
  288. }
  289. EditorGUILayout.EndVertical();
  290. /// Auto/Manual Spawn
  291. EditorGUILayout.BeginVertical(fieldBox);
  292. EditorGUILayout.PropertyField(autoSpawnObjects);
  293. EditorGUILayout.EndVertical();
  294. if (EditorGUI.EndChangeCheck())
  295. {
  296. serializedObject.ApplyModifiedProperties();
  297. }
  298. }
  299. /// <summary>
  300. /// Create a basic rendered list of objects from a SerializedProperty list or array, with Add/Destroy buttons.
  301. /// </summary>
  302. /// <param name="list"></param>
  303. /// <param name="gc"></param>
  304. public void EditableReferenceList(SerializedProperty list, GUIContent gc, GUIStyle style = null)
  305. {
  306. EditorGUILayout.LabelField(gc);
  307. if (style == null)
  308. style = new GUIStyle("HelpBox") { padding = new RectOffset(6, 6, 6, 6) };
  309. EditorGUILayout.BeginVertical(style);
  310. int count = list.arraySize;
  311. if (count == 0)
  312. {
  313. if (GUI.Button(EditorGUILayout.GetControlRect(GUILayout.MaxWidth(20)), "+", (GUIStyle)"minibutton"))
  314. {
  315. int newindex = list.arraySize;
  316. list.InsertArrayElementAtIndex(0);
  317. list.GetArrayElementAtIndex(0).objectReferenceValue = null;
  318. }
  319. }
  320. else
  321. {
  322. // List Elements and Delete buttons
  323. for (int i = 0; i < count; ++i)
  324. {
  325. EditorGUILayout.BeginHorizontal();
  326. bool add = (GUI.Button(EditorGUILayout.GetControlRect(GUILayout.MaxWidth(20)), "+", (GUIStyle)"minibutton"));
  327. EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i), GUIContent.none);
  328. bool remove = (GUI.Button(EditorGUILayout.GetControlRect(GUILayout.MaxWidth(20)), "x", (GUIStyle)"minibutton"));
  329. EditorGUILayout.EndHorizontal();
  330. if (add)
  331. {
  332. Add(list, i);
  333. break;
  334. }
  335. if (remove)
  336. {
  337. list.DeleteArrayElementAtIndex(i);
  338. //EditorGUILayout.EndHorizontal();
  339. break;
  340. }
  341. }
  342. EditorGUILayout.GetControlRect(false, 4);
  343. if (GUI.Button(EditorGUILayout.GetControlRect(), "Add", (GUIStyle)"minibutton"))
  344. Add(list, count);
  345. }
  346. EditorGUILayout.EndVertical();
  347. }
  348. private void Add(SerializedProperty list, int i)
  349. {
  350. {
  351. int newindex = list.arraySize;
  352. list.InsertArrayElementAtIndex(i);
  353. list.GetArrayElementAtIndex(i).objectReferenceValue = null;
  354. }
  355. }
  356. }
  357. #endif
  358. }