using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.Events; namespace Unity.Tutorials.Core.Editor { /// /// A generic event for signaling changes in a criterion. /// Parameters: sender. /// [Serializable] public class CriterionEvent : UnityEvent { } /// /// Base class for Criterion implementations. /// public abstract class Criterion : ScriptableObject { /// /// Raised when any Criterion is completed. /// public static CriterionEvent CriterionCompleted = new CriterionEvent(); /// /// Raised when any Criterion is invalidated. /// public static CriterionEvent CriterionInvalidated = new CriterionEvent(); /// /// Raised when this criterion is completed. /// [Header("Events")] public CriterionEvent Completed; /// /// Raised when this criterion is invalidated. /// public CriterionEvent Invalidated; bool m_Completed; /// /// Is the Criterion completed. Setting this raises CriterionCompleted/CriterionInvalidated. /// public bool IsCompleted { get { return m_Completed; } internal set { if (value == m_Completed) return; m_Completed = value; if (m_Completed) { Completed?.Invoke(this); CriterionCompleted?.Invoke(this); } else { Invalidated?.Invoke(this); CriterionInvalidated?.Invoke(this); } } } /// /// Resets the completion state. /// public void ResetCompletionState() { m_Completed = false; } /// /// Starts testing of the criterion. /// public virtual void StartTesting() { } /// /// Stops testing of the criterion. /// public virtual void StopTesting() { } /// /// Runs update logic for the criterion. /// public virtual void UpdateCompletion() { IsCompleted = EvaluateCompletion(); } /// /// Evaluates if the criterion is completed. /// /// protected virtual bool EvaluateCompletion() { throw new NotImplementedException($"Missing implementation of EvaluateCompletion in: {GetType()}"); } /// /// Auto-completes the criterion. /// /// True if the auto-completion succeeded. public abstract bool AutoComplete(); /// /// Returns FutureObjectReference for this Criterion. /// /// protected virtual IEnumerable GetFutureObjectReferences() { return Enumerable.Empty(); } /// /// Destroys unreferenced future references. /// /// /// https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnValidate.html /// protected virtual void OnValidate() { // Find instanceIDs of referenced future references var referencedFutureReferenceInstanceIDs = new HashSet(); foreach (var futureReference in GetFutureObjectReferences()) referencedFutureReferenceInstanceIDs.Add(futureReference.GetInstanceID()); // Destroy unreferenced future references var assetPath = AssetDatabase.GetAssetPath(this); var assets = AssetDatabase.LoadAllAssetsAtPath(assetPath); foreach (var asset in assets) { if (asset is FutureObjectReference && ((FutureObjectReference)asset).Criterion == this && !referencedFutureReferenceInstanceIDs.Contains(asset.GetInstanceID())) { DestroyImmediate(asset, true); } } } /// /// Creates a default FutureObjectReference for this Criterion. /// /// protected FutureObjectReference CreateFutureObjectReference() { return CreateFutureObjectReference("Future Reference"); } /// /// Creates a FutureObjectReference by specific name for this Criterion. /// /// /// protected FutureObjectReference CreateFutureObjectReference(string referenceName) { var futureReference = CreateInstance(); futureReference.Criterion = this; futureReference.ReferenceName = referenceName; var assetPath = AssetDatabase.GetAssetPath(this); AssetDatabase.AddObjectToAsset(futureReference, assetPath); return futureReference; } /// /// Updates names of the references. /// protected void UpdateFutureObjectReferenceNames() { // Update future reference names in next editor update due to AssetDatase interactions EditorApplication.update += UpdateFutureObjectReferenceNamesPostponed; } void UpdateFutureObjectReferenceNamesPostponed() { // Unsubscribe immediately since it should only be called once EditorApplication.update -= UpdateFutureObjectReferenceNamesPostponed; var assetPath = AssetDatabase.GetAssetPath(this); var tutorialPage = (TutorialPage)AssetDatabase.LoadMainAssetAtPath(assetPath); var futureReferences = AssetDatabase.LoadAllAssetsAtPath(assetPath) .Where(o => o is FutureObjectReference) .Cast(); foreach (var futureReference in futureReferences) tutorialPage.UpdateFutureObjectReferenceName(futureReference); } } }