123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- // --------------------------------------------------------------------------------------------------------------------
- // <copyright file="OnJoinedInstantiate.cs" company="Exit Games GmbH">
- // Part of: Photon Unity Utilities,
- // </copyright>
- // <summary>
- // This component will instantiate a network GameObject when a room is joined
- // </summary>
- // <author>developer@exitgames.com</author>
- // --------------------------------------------------------------------------------------------------------------------
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Serialization;
- using Photon.Realtime;
- #if UNITY_EDITOR
- using UnityEditor;
- #endif
- namespace Photon.Pun.UtilityScripts
- {
- /// <summary>
- /// This component will instantiate a network GameObject when a room is joined
- /// </summary>
- public class OnJoinedInstantiate : MonoBehaviour
- , IMatchmakingCallbacks
- {
- public enum SpawnSequence { Connection, Random, RoundRobin }
- #region Inspector Items
- // Old field, only here for backwards compat. Value copies over to SpawnPoints in OnValidate
- [HideInInspector] private Transform SpawnPosition;
- [HideInInspector] public SpawnSequence Sequence = SpawnSequence.Connection;
- [HideInInspector] public List<Transform> SpawnPoints = new List<Transform>(1) { null };
- [Tooltip("Add a random variance to a spawn point position. GetRandomOffset() can be overridden with your own method for producing offsets.")]
- [HideInInspector] public bool UseRandomOffset = true;
- [Tooltip("Radius of the RandomOffset.")]
- [FormerlySerializedAs("PositionOffset")]
- [HideInInspector] public float RandomOffset = 2.0f;
- [Tooltip("Disables the Y axis of RandomOffset. The Y value of the spawn point will be used.")]
- [HideInInspector] public bool ClampY = true;
- [HideInInspector] public List<GameObject> PrefabsToInstantiate = new List<GameObject>(1) { null }; // set in inspector
- [FormerlySerializedAs("autoSpawnObjects")]
- [HideInInspector] public bool AutoSpawnObjects = true;
- #endregion
- // Record of spawned objects, used for Despawn All
- public Stack<GameObject> SpawnedObjects = new Stack<GameObject>();
- protected int spawnedAsActorId;
- #if UNITY_EDITOR
- protected void OnValidate()
- {
- /// Check the prefab to make sure it is the actual resource, and not a scene object or other instance.
- if (PrefabsToInstantiate != null)
- for (int i = 0; i < PrefabsToInstantiate.Count; ++i)
- {
- var prefab = PrefabsToInstantiate[i];
- if (prefab)
- PrefabsToInstantiate[i] = ValidatePrefab(prefab);
- }
- /// Move any values from old SpawnPosition field to new SpawnPoints
- if (SpawnPosition)
- {
- if (SpawnPoints == null)
- SpawnPoints = new List<Transform>();
- SpawnPoints.Add(SpawnPosition);
- SpawnPosition = null;
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="prefab"></param>
- public bool AddPrefabToList(GameObject prefab)
- {
- var validated = ValidatePrefab(prefab);
- if (validated)
- {
- // Don't add to list if this prefab already is on the list
- if (PrefabsToInstantiate.Contains(validated))
- return false;
- // First try to use any null array slots to keep things tidy
- if (PrefabsToInstantiate.Contains(null))
- PrefabsToInstantiate[PrefabsToInstantiate.IndexOf(null)] = validated;
- // Otherwise, just add this prefab.
- else
- PrefabsToInstantiate.Add(validated);
- return true;
- }
- return false;
- }
- /// <summary>
- /// Determines if the supplied GameObject is an instance of a prefab, or the actual source Asset,
- /// and returns a best guess at the actual resource the dev intended to use.
- /// </summary>
- /// <returns></returns>
- protected static GameObject ValidatePrefab(GameObject unvalidated)
- {
- if (unvalidated == null)
- return null;
- if (!unvalidated.GetComponent<PhotonView>())
- return null;
- #if UNITY_2018_3_OR_NEWER
- GameObject validated = null;
- if (unvalidated != null)
- {
- if (PrefabUtility.IsPartOfPrefabAsset(unvalidated))
- return unvalidated;
- var prefabStatus = PrefabUtility.GetPrefabInstanceStatus(unvalidated);
- var isValidPrefab = prefabStatus == PrefabInstanceStatus.Connected || prefabStatus == PrefabInstanceStatus.Disconnected;
- if (isValidPrefab)
- validated = PrefabUtility.GetCorrespondingObjectFromSource(unvalidated) as GameObject;
- else
- return null;
- if (!PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(validated).Contains("/Resources"))
- Debug.LogWarning("Player Prefab needs to be a Prefab in a Resource folder.");
- }
- #else
- GameObject validated = unvalidated;
- if (unvalidated != null && PrefabUtility.GetPrefabType(unvalidated) != PrefabType.Prefab)
- validated = PrefabUtility.GetPrefabParent(unvalidated) as GameObject;
- #endif
- return validated;
- }
- #endif
- public virtual void OnEnable()
- {
- PhotonNetwork.AddCallbackTarget(this);
- }
- public virtual void OnDisable()
- {
- PhotonNetwork.RemoveCallbackTarget(this);
- }
- public virtual void OnJoinedRoom()
- {
- // Only AutoSpawn if we are a new ActorId. Rejoining should reproduce the objects by server instantiation.
- if (AutoSpawnObjects && !PhotonNetwork.LocalPlayer.HasRejoined)
- {
- SpawnObjects();
- }
- }
- public virtual void SpawnObjects()
- {
- if (this.PrefabsToInstantiate != null)
- {
- foreach (GameObject o in this.PrefabsToInstantiate)
- {
- if (o == null)
- continue;
- #if UNITY_EDITOR
- Debug.Log("Auto-Instantiating: " + o.name);
- #endif
- Vector3 spawnPos; Quaternion spawnRot;
- GetSpawnPoint(out spawnPos, out spawnRot);
- var newobj = PhotonNetwork.Instantiate(o.name, spawnPos, spawnRot, 0);
- SpawnedObjects.Push(newobj);
- }
- }
- }
- /// <summary>
- /// Destroy all objects that have been spawned by this component for this client.
- /// </summary>
- /// <param name="localOnly">Use Object.Destroy rather than PhotonNetwork.Destroy.</param>
- public virtual void DespawnObjects(bool localOnly)
- {
- while (SpawnedObjects.Count > 0)
- {
- var go = SpawnedObjects.Pop();
- if (go)
- {
- if (localOnly)
- Object.Destroy(go);
- else
- PhotonNetwork.Destroy(go);
- }
- }
- }
- public virtual void OnFriendListUpdate(List<FriendInfo> friendList) { }
- public virtual void OnCreatedRoom() { }
- public virtual void OnCreateRoomFailed(short returnCode, string message) { }
- public virtual void OnJoinRoomFailed(short returnCode, string message) { }
- public virtual void OnJoinRandomFailed(short returnCode, string message) { }
- public virtual void OnLeftRoom() { }
- protected int lastUsedSpawnPointIndex = -1;
- /// <summary>
- /// Gets the next SpawnPoint from the list using the SpawnSequence, and applies RandomOffset (if used) to the transform matrix.
- /// Override this method with any custom code for coming up with a spawn location. This method is used by AutoSpawn.
- /// </summary>
- public virtual void GetSpawnPoint(out Vector3 spawnPos, out Quaternion spawnRot)
- {
- // Fetch a point using the Sequence method indicated
- Transform point = GetSpawnPoint();
- if (point != null)
- {
- spawnPos = point.position;
- spawnRot = point.rotation;
- }
- else
- {
- spawnPos = new Vector3(0, 0, 0);
- spawnRot = new Quaternion(0, 0, 0, 1);
- }
-
- if (UseRandomOffset)
- {
- Random.InitState((int)(Time.time * 10000));
- spawnPos += GetRandomOffset();
- }
- }
-
- /// <summary>
- /// Get the transform of the next SpawnPoint from the list, selected using the SpawnSequence setting.
- /// RandomOffset is not applied, only the transform of the SpawnPoint is returned.
- /// Override this method to change how Spawn Point transform is selected. Return the transform you want to use as a spawn point.
- /// </summary>
- /// <returns></returns>
- protected virtual Transform GetSpawnPoint()
- {
- // Fetch a point using the Sequence method indicated
- if (SpawnPoints == null || SpawnPoints.Count == 0)
- {
- return null;
- }
- else
- {
- switch (Sequence)
- {
- case SpawnSequence.Connection:
- {
- int id = PhotonNetwork.LocalPlayer.ActorNumber;
- return SpawnPoints[(id == -1) ? 0 : id % SpawnPoints.Count];
- }
- case SpawnSequence.RoundRobin:
- {
- lastUsedSpawnPointIndex++;
- if (lastUsedSpawnPointIndex >= SpawnPoints.Count)
- lastUsedSpawnPointIndex = 0;
- /// Use Vector.Zero and Quaternion.Identity if we are dealing with no or a null spawnpoint.
- return SpawnPoints == null || SpawnPoints.Count == 0 ? null : SpawnPoints[lastUsedSpawnPointIndex];
- }
- case SpawnSequence.Random:
- {
- return SpawnPoints[Random.Range(0, SpawnPoints.Count)];
- }
- default:
- return null;
- }
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- protected virtual Vector3 GetRandomOffset()
- {
- Vector3 random = Random.insideUnitSphere;
- if (ClampY)
- random.y = 0;
- return RandomOffset * random.normalized;
- }
- }
- #if UNITY_EDITOR
- [CustomEditor(typeof(OnJoinedInstantiate), true)]
- [CanEditMultipleObjects]
- public class OnJoinedInstantiateEditor : Editor
- {
- SerializedProperty SpawnPoints, PrefabsToInstantiate, UseRandomOffset, ClampY, RandomOffset, Sequence, autoSpawnObjects;
- GUIStyle fieldBox;
- private void OnEnable()
- {
- SpawnPoints = serializedObject.FindProperty("SpawnPoints");
- PrefabsToInstantiate = serializedObject.FindProperty("PrefabsToInstantiate");
- UseRandomOffset = serializedObject.FindProperty("UseRandomOffset");
- ClampY = serializedObject.FindProperty("ClampY");
- RandomOffset = serializedObject.FindProperty("RandomOffset");
- Sequence = serializedObject.FindProperty("Sequence");
- autoSpawnObjects = serializedObject.FindProperty("AutoSpawnObjects");
- }
- public override void OnInspectorGUI()
- {
- base.OnInspectorGUI();
- const int PAD = 6;
- if (fieldBox == null)
- fieldBox = new GUIStyle("HelpBox") { padding = new RectOffset(PAD, PAD, PAD, PAD) };
- EditorGUI.BeginChangeCheck();
- EditableReferenceList(PrefabsToInstantiate, new GUIContent(PrefabsToInstantiate.displayName, PrefabsToInstantiate.tooltip), fieldBox);
- EditableReferenceList(SpawnPoints, new GUIContent(SpawnPoints.displayName, SpawnPoints.tooltip), fieldBox);
- /// Spawn Pattern
- EditorGUILayout.BeginVertical(fieldBox);
- EditorGUILayout.PropertyField(Sequence);
- EditorGUILayout.PropertyField(UseRandomOffset);
- if (UseRandomOffset.boolValue)
- {
- EditorGUILayout.PropertyField(RandomOffset);
- EditorGUILayout.PropertyField(ClampY);
- }
- EditorGUILayout.EndVertical();
- /// Auto/Manual Spawn
- EditorGUILayout.BeginVertical(fieldBox);
- EditorGUILayout.PropertyField(autoSpawnObjects);
- EditorGUILayout.EndVertical();
- if (EditorGUI.EndChangeCheck())
- {
- serializedObject.ApplyModifiedProperties();
- }
- }
- /// <summary>
- /// Create a basic rendered list of objects from a SerializedProperty list or array, with Add/Destroy buttons.
- /// </summary>
- /// <param name="list"></param>
- /// <param name="gc"></param>
- public void EditableReferenceList(SerializedProperty list, GUIContent gc, GUIStyle style = null)
- {
- EditorGUILayout.LabelField(gc);
- if (style == null)
- style = new GUIStyle("HelpBox") { padding = new RectOffset(6, 6, 6, 6) };
- EditorGUILayout.BeginVertical(style);
- int count = list.arraySize;
- if (count == 0)
- {
- if (GUI.Button(EditorGUILayout.GetControlRect(GUILayout.MaxWidth(20)), "+", (GUIStyle)"minibutton"))
- {
- int newindex = list.arraySize;
- list.InsertArrayElementAtIndex(0);
- list.GetArrayElementAtIndex(0).objectReferenceValue = null;
- }
- }
- else
- {
- // List Elements and Delete buttons
- for (int i = 0; i < count; ++i)
- {
- EditorGUILayout.BeginHorizontal();
- bool add = (GUI.Button(EditorGUILayout.GetControlRect(GUILayout.MaxWidth(20)), "+", (GUIStyle)"minibutton"));
- EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i), GUIContent.none);
- bool remove = (GUI.Button(EditorGUILayout.GetControlRect(GUILayout.MaxWidth(20)), "x", (GUIStyle)"minibutton"));
- EditorGUILayout.EndHorizontal();
- if (add)
- {
- Add(list, i);
- break;
- }
- if (remove)
- {
- list.DeleteArrayElementAtIndex(i);
- //EditorGUILayout.EndHorizontal();
- break;
- }
- }
- EditorGUILayout.GetControlRect(false, 4);
-
- if (GUI.Button(EditorGUILayout.GetControlRect(), "Add", (GUIStyle)"minibutton"))
- Add(list, count);
- }
-
- EditorGUILayout.EndVertical();
- }
- private void Add(SerializedProperty list, int i)
- {
- {
- int newindex = list.arraySize;
- list.InsertArrayElementAtIndex(i);
- list.GetArrayElementAtIndex(i).objectReferenceValue = null;
- }
- }
- }
-
- #endif
- }
|