You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

451 lines
16 KiB

// --------------------------------------------------------------------------------------------------------------------
// <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
}