451 lines
16 KiB
C#
451 lines
16 KiB
C#
// --------------------------------------------------------------------------------------------------------------------
|
|
// <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
|
|
}
|