using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using UnityObject = UnityEngine.Object; namespace Unity.Tutorials.Core.Editor { /// /// Criterion for checking that a specific prefab is instantiated. /// public class InstantiatePrefabCriterion : Criterion { [SerializeField] GameObject m_PrefabParent; [SerializeField] FuturePrefabInstanceCollection m_FuturePrefabInstances = new FuturePrefabInstanceCollection(); // InstanceID's of existing GameObject prefab instance roots we want to ignore HashSet m_ExistingPrefabInstances = new HashSet(); // InstanceID of GameObject prefab instance root that initially completed this criterion int m_PrefabInstance; /// /// Prefab parent. /// public GameObject PrefabParent { get => m_PrefabParent; set { m_PrefabParent = value; OnValidate(); } } /// /// Sets future prefab instances. /// /// public void SetFuturePrefabInstances(IList prefabParents) { var futurePrefabInstances = prefabParents.Select(prefabParent => new FuturePrefabInstance(prefabParent)); m_FuturePrefabInstances.SetItems(futurePrefabInstances.ToList()); OnValidate(); } /// /// Runs validation logic. /// protected override void OnValidate() { base.OnValidate(); if (m_PrefabParent == null) return; // Ensure prefab parent is infact a prefab parent if (PrefabUtility.GetPrefabAssetType(m_PrefabParent) != PrefabAssetType.NotAPrefab) { // Ensure prefab parent is the prefab root var prefabRoot = m_PrefabParent.transform.root.gameObject; if (m_PrefabParent != prefabRoot) m_PrefabParent = prefabRoot; } else { Debug.LogWarning("Prefab parent must either be a prefab parent or a prefab instance."); m_PrefabParent = null; } // Prevent aliasing of future reference whenever the last item is copied var count = m_FuturePrefabInstances.Count; if (count >= 2) { var last = m_FuturePrefabInstances[count - 1]; var secondLast = m_FuturePrefabInstances[count - 2]; if (last.FutureReference == secondLast.FutureReference) last.FutureReference = null; } var updateFutureReferenceNames = false; var futurePrefabInstanceIndex = -1; foreach (var futurePrefabInstance in m_FuturePrefabInstances) { futurePrefabInstanceIndex++; // Destroy future reference if prefab parent is null or it changed var prefabParent = futurePrefabInstance.PrefabParent; var previousPrefabParent = futurePrefabInstance.PreviousPrefabParent; futurePrefabInstance.PreviousPrefabParent = prefabParent; if (prefabParent == null || (previousPrefabParent != null && prefabParent != previousPrefabParent)) { if (futurePrefabInstance.FutureReference != null) { DestroyImmediate(futurePrefabInstance.FutureReference, true); futurePrefabInstance.FutureReference = null; } } if (prefabParent == null) continue; // Ensure future prefab parent is infact a prefab parent if (PrefabUtility.GetPrefabAssetType(prefabParent) != PrefabAssetType.NotAPrefab) { // Find root game object of future prefab parent GameObject futurePrefabParentRoot = null; if (prefabParent is GameObject) { var gameObject = (GameObject)prefabParent; futurePrefabParentRoot = gameObject.transform.root.gameObject; } else if (prefabParent is Component) { var component = (Component)prefabParent; futurePrefabParentRoot = component.transform.root.gameObject; } // Ensure prefab parent and future prefab parent belong to the same prefab if (futurePrefabParentRoot == m_PrefabParent) { // Create new future reference if it doesn't exist yet if (futurePrefabInstance.FutureReference == null) { var referenceName = string.Format("{0}: {1} ({2})", futurePrefabInstanceIndex + 1, prefabParent.name, prefabParent.GetType().Name); futurePrefabInstance.FutureReference = CreateFutureObjectReference(referenceName); updateFutureReferenceNames = true; } } else { Debug.LogWarning("Prefab parent and future prefab parent have different prefab objects."); futurePrefabInstance.PrefabParent = null; } } else { Debug.LogWarning("Future prefab parent must be either a prefab parent or a prefab instance."); futurePrefabInstance.PrefabParent = null; } } if (updateFutureReferenceNames) UpdateFutureObjectReferenceNames(); } /// /// Starts testing of the criterion. /// public override void StartTesting() { // Record existing prefab instances m_ExistingPrefabInstances.Clear(); foreach (var gameObject in UnityObject.FindObjectsOfType()) { if (PrefabUtilityShim.GetCorrespondingObjectFromSource(gameObject) != null) { var prefabInstanceRoot = PrefabUtility.GetOutermostPrefabInstanceRoot(gameObject); m_ExistingPrefabInstances.Add(prefabInstanceRoot.GetInstanceID()); } } Selection.selectionChanged += OnSelectionChanged; if (IsCompleted) EditorApplication.update += OnUpdateWhenCompleted; UpdateCompletion(); } /// /// Stops testing of the criterion. /// public override void StopTesting() { m_ExistingPrefabInstances.Clear(); Selection.selectionChanged -= OnSelectionChanged; EditorApplication.update -= OnUpdateWhenCompleted; } void OnSelectionChanged() { if (IsCompleted) return; foreach (var gameObject in Selection.gameObjects) { if (PrefabUtilityShim.GetCorrespondingObjectFromSource(gameObject) != null) { var prefabInstanceRoot = PrefabUtility.GetOutermostPrefabInstanceRoot(gameObject); if (prefabInstanceRoot == gameObject && m_ExistingPrefabInstances.Add(prefabInstanceRoot.GetInstanceID())) OnPrefabInstantiated(prefabInstanceRoot); } } } void OnPrefabInstantiated(GameObject prefabInstanceRoot) { if (m_PrefabParent == null) return; if (PrefabUtilityShim.GetCorrespondingObjectFromSource(prefabInstanceRoot) == m_PrefabParent) { foreach (var component in prefabInstanceRoot.GetComponentsInChildren()) { UpdateFutureReferences(component); if (component is Transform) UpdateFutureReferences(component.gameObject); } m_PrefabInstance = prefabInstanceRoot.GetInstanceID(); UpdateCompletion(); } } void OnUpdateWhenCompleted() { if (!IsCompleted) { EditorApplication.update -= OnUpdateWhenCompleted; return; } UpdateCompletion(); } bool EvaluateCompletionInternal() { if (m_PrefabInstance == 0) return false; var prefabObject = EditorUtility.InstanceIDToObject(m_PrefabInstance); if (prefabObject == null) { m_ExistingPrefabInstances.Remove(m_PrefabInstance); m_PrefabInstance = 0; return false; } return true; } /// /// Evaluates if the criterion is completed. /// /// protected override bool EvaluateCompletion() { var willBeCompleted = EvaluateCompletionInternal(); if (!IsCompleted && willBeCompleted) EditorApplication.update += OnUpdateWhenCompleted; return willBeCompleted; } void UpdateFutureReferences(UnityObject prefabInstance) { UnityObject prefabParent = PrefabUtilityShim.GetCorrespondingObjectFromSource(prefabInstance); foreach (var futurePrefabInstance in m_FuturePrefabInstances) { if (futurePrefabInstance.PrefabParent == prefabParent) futurePrefabInstance.FutureReference.SceneObjectReference.Update(prefabInstance); } } /// /// Returns FutureObjectReference for this Criterion. /// /// protected override IEnumerable GetFutureObjectReferences() { return m_FuturePrefabInstances .Select(futurePrefabInstance => futurePrefabInstance.FutureReference) .Where(futurePrefabInstance => futurePrefabInstance != null); } /// /// Auto-completes the criterion. /// /// True if the auto-completion succeeded. public override bool AutoComplete() { if (m_PrefabParent == null) return false; Selection.activeObject = PrefabUtility.InstantiatePrefab(m_PrefabParent); return true; } /// /// Future prefab instance. /// [Serializable] public class FuturePrefabInstance { [SerializeField] UnityObject m_PrefabParent; UnityObject m_PreviousPrefabParent; [SerializeField, HideInInspector] FutureObjectReference m_FutureReference; /// /// Prefab parent. /// public UnityObject PrefabParent { get => m_PrefabParent; set => m_PrefabParent = value; } /// /// Previous prefab parent. /// public UnityObject PreviousPrefabParent { get => m_PreviousPrefabParent; set => m_PreviousPrefabParent = value; } /// /// Future reference. /// public FutureObjectReference FutureReference { get => m_FutureReference; set => m_FutureReference = value; } /// /// Construcs with a specific prefab parent. /// /// public FuturePrefabInstance(UnityObject prefabParent) { m_PrefabParent = prefabParent; } } [Serializable] class FuturePrefabInstanceCollection : CollectionWrapper { } } }