mvaganov

Timer.cs

Oct 24th, 2017
251
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. #if UNITY_EDITOR
  5. using UnityEditor;
  6. using System.Linq;
  7. using System.IO;
  8. #endif
  9.  
  10. // author: mvaganov@hotmail.com
  11. // license: Copyfree, public domain. This is free code! Great artists, steal this code!
  12. // latest version at: https://pastebin.com/raw/h61nAC3E -- pause (2019/05/10)
  13. namespace NS {
  14.     /* // example code:
  15.     NS.Timer.setTimeout (() => {
  16.         Debug.Log("This will print 3 seconds after setTimeout was called!");
  17.     }, 3000);
  18.     */
  19.     public class Timer : MonoBehaviour {
  20.         [Tooltip("When to trigger")]
  21.         public float seconds = 1;
  22.         [Tooltip("Transform to teleport to\nSceneAsset to load a new scene\nAudioClip to play audio\nGameObject to SetActivate(true)")]
  23.         public ObjectPtr whatToActivate = new ObjectPtr();
  24.         [Tooltip("restart a timer after triggering")]
  25.         public bool repeat = false;
  26.         [Tooltip("attempt to deactivate instead of activate")]
  27.         public bool deactivate = false;
  28.  
  29.         private void DoTimer() {
  30.             if(repeat) {
  31.                 Chrono.setTimeout(DoTimer, (long)(seconds * 1000));
  32.             }
  33.             Chrono.ToDo todo = Chrono.setTimeout(whatToActivate.Data, (long)(seconds * 1000));
  34.             todo.who = this;
  35.             todo.activate = !deactivate;
  36.         }
  37.  
  38.         void Start() { if(whatToActivate.Data != null) { DoTimer(); } }
  39.  
  40.         /// <summary>Allows implicit conversion of lambda expressions and delegates. Same as Chrono.setTimeout, this method is here to prevent warnings.</summary>
  41.         /// <param name="action">Action. what to do</param>
  42.         /// <param name="delayMilliseconds">Delay milliseconds. in how-many-milliseconds to do it</param>
  43.         public static void setTimeout(System.Action action, long delayMilliseconds) {
  44.             Chrono.setTimeout(action, delayMilliseconds);
  45.         }
  46.     }
  47.  
  48.     public class Chrono : MonoBehaviour {
  49.         // https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer?redirectedfrom=MSDN&view=netframework-4.8
  50.         // explicitly NOT using System.Timers.Timer because it's multi-threaded, and many Unity methods, notably the time keeping ones, must be called from the main thread.
  51.  
  52.         /// The singleton
  53.         private static Chrono s_instance = null;
  54.         [Tooltip("keeps track of how long each update takes. If a timer-update takes longer than this, stop executing events and do them later. Less than 0 for no limit, 0 for one event per update.")]
  55.         public int maxMillisecondsPerUpdate = 100;
  56.         private long maxTicksPerUpdate;
  57.         /// queue of things to do using game time. use this for in-game events that can be paused or slowed down with time dialation.
  58.         public List<ToDo> queue = new List<ToDo>();
  59.         /// queue of things to do using real-time game time. use this for UI events, or things that use the real-world as a reference, that should always work at the same rate
  60.         public List<ToDo> queueRealtime = new List<ToDo>();
  61.         /// While this is zero, use system time. As soon as time becomes perturbed, by pause or time scale, keep track of game-time. To reset time back to realtime, use SynchToRealtime()
  62.         private long alternativeTicks = 0;
  63.         [Tooltip("stop advancing time & executing the queue?")]
  64.         public bool paused = false;
  65.         private bool pausedLastFrame = false;
  66.         /// The timer counts in milliseconds, Unity measures in fractions of a second. This value reconciles fractional milliseconds.
  67.         private float leftOverTime = 0;
  68.         /// if actions are interrupted, probably by a deadline, this keeps track of what was being done
  69.         private List<ToDo> _currentlyDoing = new List<ToDo>();
  70.         private int currentlyDoneThingIndex = 0;
  71.         [Tooltip("do this when the timer is paused")]
  72.         public UnityEngine.Events.UnityEvent onPause;
  73.         [Tooltip("do this when the timer is unpaused")]
  74.         public UnityEngine.Events.UnityEvent onUnpause;
  75.  
  76.         [System.Serializable]
  77.         public class ToDo {
  78.             public string description;
  79.             /// Unix Epoch Time Milliseconds
  80.             public long when;
  81.             /// could be a delegate, or an executable object
  82.             public object what;
  83.             /// a parameter for 'who' wants this particular 'what' to be done, like a context.
  84.             public object who;
  85.             /// whether or not to DO or UN-do
  86.             public bool activate = true;
  87.             /// what could be a delegate, or an executable object, as executed by a Trigger
  88.             public ToDo(long when, object what, string description = null, object who = null) {
  89.                 if(description == null) {
  90.                     if(what != null && typeof(System.Action).IsAssignableFrom(what.GetType())) {
  91.                         System.Action a = what as System.Action;
  92.                         description = a.Method.Name;
  93.                     } else if(what != null) {
  94.                         description = what.ToString();
  95.                     }
  96.                 }
  97.                 this.description = description; this.when = when; this.what = what; this.who = who;
  98.             }
  99.             /// comparer, used to sort into a list
  100.             public class Comparer : IComparer<ToDo> {
  101.                 public int Compare(ToDo x, ToDo y) { return x.when.CompareTo(y.when); }
  102.             }
  103.             public static Comparer compare = new Comparer();
  104.         }
  105.         public static Chrono Instance() {
  106.             if(s_instance == null) {
  107.                 Object[] objs = FindObjectsOfType(typeof(Chrono));  // find the instance
  108.                 for(int i = 0; i < objs.Length; ++i) {
  109.                     if(objs[i].GetType() == typeof(Chrono)) {
  110.                         s_instance = objs[i] as Chrono; break;
  111.                     }
  112.                 }
  113.                 if(s_instance == null) { // if it doesn't exist
  114.                     GameObject g = new GameObject("<" + typeof(Chrono).Name + ">");
  115.                     s_instance = g.AddComponent<Chrono>(); // create one
  116.                 }
  117.             }
  118.             return s_instance;
  119.         }
  120.  
  121.         /// Unix Time: milliseconds since Jan 1 1970
  122.         public static long NowRealtime { get { return System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond; } }
  123.         /// Unix Time: 'ticks' since Jan 1 1970 (microseconds). faster and more accurate.
  124.         public static long NowRealTicks { get { return System.DateTime.Now.Ticks; } }
  125.  
  126.         /// game time right now (modified by pausing or Time.timeScale)
  127.         public long now { get { return (alternativeTicks == 0) ? NowRealtime : alternativeTicks / System.TimeSpan.TicksPerMillisecond; } }
  128.         public long nowTicks { get { return (alternativeTicks == 0) ? NowRealTicks : alternativeTicks; } }
  129.  
  130.         /// game time right now (modified by pausing or Time.timeScale)
  131.         public static long Now { get { return Instance().now; } }
  132.         public static long NowTicks { get { return Instance().nowTicks; } }
  133.  
  134.         /// clears the difference between game time and real time
  135.         public void SyncToRealtime() { alternativeTicks = 0; }
  136.  
  137.         private int BestIndexFor(long soon, List<ToDo> a_queue) {
  138.             int index = 0;
  139.             if(a_queue.Count < 8) { // for small lists (which happen A LOT), linear search is fine.
  140.                 for(index = 0; index < a_queue.Count; ++index) {
  141.                     if(a_queue[index].when > soon) break;
  142.                 }
  143.             } else {
  144.                 ToDo toInsert = new ToDo(soon, null);
  145.                 index = a_queue.BinarySearch(toInsert);
  146.                 if(index < 0) { index = ~index; }
  147.             }
  148.             return index;
  149.         }
  150.  
  151.         /// <summary>as the JavaScript function</summary>
  152.         /// <param name="action">Action. an object to trigger, expected to be a delegate or System.Action</param>
  153.         /// <param name="delayMilliseconds">Delay milliseconds.</param>
  154.         public ToDo SetTimeout(System.Action action, long delayMilliseconds) {
  155.             return SetTimeout((object)action, delayMilliseconds);
  156.         }
  157.         /// <summary>as the JavaScript function</summary>
  158.         /// <param name="action">Action. an object to trigger, expected to be a delegate or System.Action</param>
  159.         /// <param name="delayMilliseconds">Delay milliseconds.</param>
  160.         public ToDo SetTimeout(object action, long delayMilliseconds) {
  161.             long soon = nowTicks + delayMilliseconds * System.TimeSpan.TicksPerMillisecond;
  162.             ToDo todo = new ToDo(soon, action);
  163.             queue.Insert(BestIndexFor(soon, queue), todo);
  164.             return todo;
  165.         }
  166.  
  167.         /// <summary>as the JavaScript function</summary>
  168.         /// <param name="action">Action. an object to trigger, expected to be a delegate or System.Action</param>
  169.         /// <param name="delayMilliseconds">Delay milliseconds.</param>
  170.         public ToDo SetTimeoutRealtime(object action, long delayMilliseconds) {
  171.             long soon = NowRealTicks + delayMilliseconds * System.TimeSpan.TicksPerMillisecond;
  172.             ToDo todo = new ToDo(soon, action);
  173.             queueRealtime.Insert(BestIndexFor(soon, queueRealtime), todo);
  174.             return todo;
  175.         }
  176.  
  177.         /// <param name="action">Action. what to do</param>
  178.         /// <param name="delayMilliseconds">Delay milliseconds. in how-many-milliseconds to do it</param>
  179.         public static ToDo setTimeout(object action, long delayMilliseconds) {
  180.             return Instance().SetTimeout(action, delayMilliseconds);
  181.         }
  182.  
  183.         /// <param name="action">Action. what to do</param>
  184.         /// <param name="delayMilliseconds">Delay milliseconds. in how-many-milliseconds to do it</param>
  185.         public static ToDo setTimeoutRealtime(object action, long delayMilliseconds) {
  186.             return Instance().SetTimeoutRealtime(action, delayMilliseconds);
  187.         }
  188.  
  189.         /// Allows implicit conversion of lambda expressions and delegates
  190.         /// <param name="action">Action. what to do</param>
  191.         /// <param name="delayMilliseconds">Delay milliseconds. in how-many-milliseconds to do it</param>
  192.         public static ToDo setTimeout(System.Action action, long delayMilliseconds) {
  193.             return Instance().SetTimeout(action, delayMilliseconds);
  194.         }
  195.  
  196.         /// <summary>Allows implicit conversion of lambda expressions and delegates</summary>
  197.         /// <param name="action">Action. what to do</param>
  198.         /// <param name="delayMilliseconds">Delay milliseconds. in how-many-milliseconds to do it</param>
  199.         public static ToDo setTimeoutRealtime(System.Action action, long delayMilliseconds) {
  200.             return Instance().SetTimeoutRealtime(action, delayMilliseconds);
  201.         }
  202.  
  203.         //void OnApplicationPause(bool paused) { if(alternativeTime == 0) { alternativeTime = now; } }
  204.         void Pause() { paused = true; }
  205.         void Unpause() { paused = false; }
  206.         void OnApplicationPause(bool paused) { if(alternativeTicks == 0) { alternativeTicks = nowTicks; } }
  207.         void OnDisable() { OnApplicationPause(true); }
  208.         void OnEnable() { OnApplicationPause(false); }
  209.  
  210.         /// used to handle pause/unpause behavior
  211.         public delegate void BooleanAction(bool b);
  212.         public static void EquateUnityEditorPauseWithApplicationPause(BooleanAction b) {
  213. #if UNITY_EDITOR
  214.             // This method is run whenever the playmode state is changed.
  215.             UnityEditor.EditorApplication.pauseStateChanged += (UnityEditor.PauseState ps) => {
  216.                 b(ps == UnityEditor.PauseState.Paused);
  217.             };
  218. #endif
  219.         }
  220.  
  221.         protected void Init() { EquateUnityEditorPauseWithApplicationPause(OnApplicationPause); }
  222.  
  223.         void Awake() {
  224.             if(s_instance != null && s_instance != this) { throw new System.Exception("There should only be one " + this.GetType()); }
  225.             s_instance = this;
  226.         }
  227.  
  228.         void RefreshTiming() { maxTicksPerUpdate = maxMillisecondsPerUpdate * System.TimeSpan.TicksPerMillisecond; }
  229.  
  230. #if UNITY_EDITOR
  231.         void OnValidate() { RefreshTiming(); }
  232. #endif
  233.  
  234.         void Start() { Init(); RefreshTiming(); }
  235.  
  236.         void Update() {
  237.             // handle pause behavior
  238.             if(paused) {
  239.                 if(!pausedLastFrame) {
  240.                     alternativeTicks = nowTicks;
  241.                     if(onPause != null) { onPause.Invoke(); }
  242.                     pausedLastFrame = true;
  243.                 }
  244.             }
  245.             else if(pausedLastFrame) {
  246.                 if(onUnpause != null) { onUnpause.Invoke(); }
  247.                 pausedLastFrame = false;
  248.             }
  249.             // pump the timer queues, both realtime (takes priority) and game-time
  250.             long now_t, nowForReals = NowRealTicks;
  251.             long deadline = nowForReals + maxTicksPerUpdate;
  252.             int thingsDone = 0;
  253.             if(queueRealtime.Count > 0) {
  254.                 thingsDone = DoWhatIsNeededNow(queueRealtime, nowForReals, deadline);
  255.             }
  256.             if(queue.Count > 0 && !paused) {
  257.                 if(alternativeTicks == 0) {
  258.                     now_t = nowForReals;
  259.                     if(Time.timeScale != 1) { alternativeTicks = now_t; }
  260.                 } else {
  261.                     float deltaTimeTicks = (Time.deltaTime * 1000 * System.TimeSpan.TicksPerMillisecond);
  262.                     long deltaTimeTicksLong = (long)(deltaTimeTicks + leftOverTime);
  263.                     alternativeTicks += deltaTimeTicksLong;
  264.                     leftOverTime = deltaTimeTicks - deltaTimeTicksLong;
  265.                     now_t = alternativeTicks;
  266.                 }
  267.                 if(thingsDone == 0 || now_t < deadline) {
  268.                     thingsDone += DoWhatIsNeededNow(queue, now_t, deadline);
  269.                 }
  270.             }
  271.         }
  272.  
  273.         int DoWhatIsNeededNow(List<ToDo> a_queue, long now_t, long deadline) {
  274.             bool tryToDoMore;
  275.             int thingsDone = 0;
  276.             do {
  277.                 tryToDoMore = false;
  278.                 if(a_queue.Count > 0 && a_queue[0].when <= now_t) {
  279.                     if(_currentlyDoing.Count == 0) {
  280.                         // the things to do in the queue might add to the queue, so to prevent infinite looping...
  281.                         // separate out the elements to do right now
  282.                         for(int i = 0; i < a_queue.Count; ++i) {
  283.                             if(a_queue[i].when > now_t) { break; }
  284.                             _currentlyDoing.Add(a_queue[i]);
  285.                         }
  286.                         // if there's nothing to do, get out of this potential loop
  287.                         if(_currentlyDoing.Count == 0) { break; }
  288.                         a_queue.RemoveRange(0, _currentlyDoing.Count);
  289.                         tryToDoMore = false;
  290.                     }
  291.                     // do what is scheduled to do right now
  292.                     while(currentlyDoneThingIndex < _currentlyDoing.Count) {
  293.                         ToDo todo = _currentlyDoing[currentlyDoneThingIndex++];
  294.                         // if DoActivate adds to the queue, it won't get executed this cycle
  295.                         NS.ActivateAnything.DoActivate(todo.what, gameObject, todo.who, todo.activate);
  296.                         ++thingsDone;
  297.                         // if it took too long to do that thing, stop and hold the rest of the things to do till later.
  298.                         if(maxTicksPerUpdate >= 0 && NowRealTicks > deadline) {
  299.                             break;
  300.                         }
  301.                     }
  302.                     if(currentlyDoneThingIndex >= _currentlyDoing.Count) {
  303.                         _currentlyDoing.Clear();
  304.                         currentlyDoneThingIndex = 0;
  305.                         tryToDoMore = NowRealTicks < deadline && a_queue.Count > 0;
  306.                     }
  307.                 }
  308.             } while(tryToDoMore);
  309.             return thingsDone;
  310.         }
  311.     }
  312.  
  313.     /// This class serves as a store for static Utility functions related to 'activating things'.
  314.     public static class ActivateAnything {
  315.         /// <summary>'Activates' something, in a ways that is possibly specific to circumstances.</summary>
  316.         /// <param name="whatToActivate">what needs to be activated. In "The straw that broke the camel's back", this is the "camel's back", which we can assume is breaking (or unbreaking?) during this function.</param>
  317.         /// <param name="causedActivate">what triggered the activation. In "The straw that broke the camel's back", this is "the straw". Depending on the straw, breaking may happen differently.</param>
  318.         /// <param name="doingActivate">what is doing the activation. In "The straw that broke the camel's back", this is "the camel".</param>
  319.         /// <param name="activate">whether to activate or deactivate. In "The straw that broke the camel's back", this is whether to break or unbreak the-camel's-back.</param>
  320.         /// <param name="delayInSeconds">Delay in seconds.</param>
  321.         public static void DoActivate(
  322.             object whatToActivate, object causedActivate, object doingActivate, bool activate,
  323.             float delayInSeconds
  324.         ) {
  325.             if(delayInSeconds <= 0) {
  326.                 DoActivate(whatToActivate, causedActivate, doingActivate, activate);
  327.             } else {
  328.                 NS.Timer.setTimeout(() => {
  329.                     DoActivate(whatToActivate, causedActivate, doingActivate, activate);
  330.                 }, (long)(delayInSeconds * 1000));
  331.             }
  332.         }
  333.  
  334.         /// <summary>'Activates' something, in a ways that is possibly specific to circumstances.</summary>
  335.         /// <param name="whatToActivate">what needs to be activated. In "The straw that broke the camel's back", this is the "camel's back", which we can assume is breaking (or unbreaking?) during this function.</param>
  336.         /// <param name="causedActivate">what triggered the activation. In "The straw that broke the camel's back", this is "the straw". Depending on the straw, breaking may happen differently.</param>
  337.         /// <param name="doingActivate">what is doing the activation. In "The straw that broke the camel's back", this is "the camel".</param>
  338.         /// <param name="activate">whether to activate or deactivate. In "The straw that broke the camel's back", this is whether to break or unbreak the-camel's-back.</param>
  339.         public static void DoActivate(
  340.             object whatToActivate, object causedActivate, object doingActivate, bool activate
  341.         ) {
  342.             if(whatToActivate == null) { Debug.LogError("Don't know how to activate null"); return; }
  343.             if(whatToActivate is IReference) {
  344.                 whatToActivate = ((IReference)whatToActivate).Dereference();
  345.             }
  346.             System.Type type = whatToActivate.GetType();
  347.             if(typeof(System.Action).IsAssignableFrom(type)) {
  348.                 System.Action a = whatToActivate as System.Action;
  349.                 a.Invoke();
  350.             } else if(typeof(UnityEngine.Events.UnityEvent).IsAssignableFrom(type)) {
  351.                 UnityEngine.Events.UnityEvent a = whatToActivate as UnityEngine.Events.UnityEvent;
  352.                 a.Invoke();
  353.             } else if(type == typeof(Transform)) {
  354.                 Transform targetLocation = ConvertToTransform(whatToActivate);
  355.                 Transform toMove = ConvertToTransform(causedActivate);
  356.                 if(toMove != null) {
  357.                     toMove.position = targetLocation.position;
  358.                 }
  359.             } else if(type == typeof(AudioClip) || type == typeof(AudioSource)) {
  360.                 AudioSource asource = null;
  361.                 if(type == typeof(AudioSource)) {
  362.                     asource = whatToActivate as AudioSource;
  363.                 }
  364.                 if(asource == null) {
  365.                     GameObject go = ConvertToGameObject(doingActivate);
  366.                     if(go != null) {
  367.                         asource = go.AddComponent<AudioSource>();
  368.                     } else {
  369.                         throw new System.Exception("can't create audio without a game object to put it on.");
  370.                     }
  371.                 }
  372.                 if(type == typeof(AudioClip)) {
  373.                     asource.clip = whatToActivate as AudioClip;
  374.                 }
  375.                 if(activate) {
  376.                     asource.Play();
  377.                 } else {
  378.                     asource.Stop();
  379.                 }
  380.             } else if(type == typeof(ParticleSystem)) {
  381.                 ParticleSystem ps = whatToActivate as ParticleSystem;
  382.                 if(activate) {
  383.                     Transform t = ps.transform;
  384.                     GameObject go = ConvertToGameObject(doingActivate);
  385.                     t.position = go.transform.position;
  386.                     t.rotation = go.transform.rotation;
  387.                     ParticleSystem.ShapeModule sm = ps.shape;
  388.                     if(sm.shapeType == ParticleSystemShapeType.Mesh) {
  389.                         sm.mesh = go.GetComponent<MeshFilter>().mesh;
  390.                         sm.scale = go.transform.lossyScale;
  391.                     } else if(sm.shapeType == ParticleSystemShapeType.MeshRenderer) {
  392.                         sm.meshRenderer = go.GetComponent<MeshRenderer>();
  393.                     }
  394.                     ps.Play();
  395.                 } else {
  396.                     ps.Stop();
  397.                 }
  398.             } else if(type == typeof(GameObject)) {
  399.                 GameObject go = (whatToActivate as GameObject);
  400.                 if(go == null) {
  401.                     Debug.LogWarning("GameObject destroyed?");
  402.                 } else {
  403.                     go.SetActive(activate);
  404.                 }
  405.             } else if(type == typeof(UnityEngine.Color)) {
  406.                 GameObject go = ConvertToGameObject(doingActivate);
  407.                 RememberedOriginalColor r = go.GetComponent<RememberedOriginalColor>();
  408.                 if(activate) {
  409.                     Color c = (Color)whatToActivate;
  410.                     if(r == null) {
  411.                         r = go.AddComponent<RememberedOriginalColor>();
  412.                         r.oldColor = go.GetComponent<Renderer>().material.color;
  413.                     }
  414.                     go.GetComponent<Renderer>().material.color = c;
  415.                 } else {
  416.                     if(r != null) {
  417.                         go.GetComponent<Renderer>().material.color = r.oldColor;
  418.                         Object.Destroy(r);
  419.                     }
  420.                 }
  421.             } else if(type == typeof(UnityEngine.Material)) {
  422.                 GameObject go = ConvertToGameObject(doingActivate);
  423.                 RememberedOriginalMaterial r = go.GetComponent<RememberedOriginalMaterial>();
  424.                 if(activate) {
  425.                     Material m = whatToActivate as Material;
  426.                     if(r == null) {
  427.                         r = go.AddComponent<RememberedOriginalMaterial>();
  428.                         r.oldMaterial = go.GetComponent<Renderer>().material;
  429.                     }
  430.                     go.GetComponent<Renderer>().material = m;
  431.                 } else {
  432.                     if(r != null) {
  433.                         go.GetComponent<Renderer>().material = r.oldMaterial;
  434.                         Object.Destroy(r);
  435.                     }
  436.                 }
  437.             } else if(typeof(IEnumerable).IsAssignableFrom(type)) {
  438.                 IEnumerable ienum = whatToActivate as IEnumerable;
  439.                 IEnumerator iter = ienum.GetEnumerator();
  440.                 while(iter.MoveNext()) {
  441.                     DoActivate(iter.Current, causedActivate, doingActivate, activate);
  442.                 }
  443.             } else if(type == typeof(Animation)) {
  444.                 if(activate) {
  445.                     (whatToActivate as Animation).Play();
  446.                 } else {
  447.                     (whatToActivate as Animation).Stop();
  448.                 }
  449.             } else {
  450.                 System.Reflection.MethodInfo[] m = type.GetMethods();
  451.                 bool invoked = false;
  452.                 for(int i = 0; i < m.Length; ++i) {
  453.                     System.Reflection.MethodInfo method = m[i];
  454.                     if((activate && method.Name == "DoActivateTrigger")
  455.                     || (!activate && method.Name == "DoDeactivateTrigger")) {
  456.                         switch(method.GetParameters().Length) {
  457.                         case 0: method.Invoke(whatToActivate, new object[] { }); invoked = true; break;
  458.                         case 1: method.Invoke(whatToActivate, new object[] { causedActivate }); invoked = true; break;
  459.                         case 2: method.Invoke(whatToActivate, new object[] { causedActivate, doingActivate }); invoked = true; break;
  460.                         }
  461.                         break;
  462.                     }
  463.                 }
  464.                 if(!invoked) {
  465.                     Debug.LogError("Don't know how to " + ((activate) ? "DoActivateTrigger" : "DoDeactivateTrigger") + " a \'" + type + "\' (" + whatToActivate + ") with \'" + doingActivate + "\', triggered by \'" + causedActivate + "\'");
  466.                 }
  467.             }
  468.         }
  469.         public class RememberedOriginalMaterial : MonoBehaviour { public Material oldMaterial; }
  470.         public class RememberedOriginalColor : MonoBehaviour { public Color oldColor; }
  471.  
  472.         public static GameObject ConvertToGameObject(object obj) {
  473.             if(obj is GameObject) { return obj as GameObject; }
  474.             if(obj is Component) { return (obj as Component).gameObject; }
  475.             if(obj is Collision) { return (obj as Collision).gameObject; }
  476.             if(obj is Collision2D) { return (obj as Collision2D).gameObject; }
  477.             return null;
  478.         }
  479.  
  480.         public static Transform ConvertToTransform(object obj) {
  481.             if(obj is Transform) { return obj as Transform; }
  482.             GameObject go = ConvertToGameObject(obj);
  483.             if(go != null) { return go.transform; }
  484.             return null;
  485.         }
  486.     }
  487.  
  488.     public interface IReference { object Dereference(); }
  489.  
  490.     [System.Serializable]
  491.     public struct ObjectPtr : IReference {
  492.         public Object data;
  493.         public Object Data { get { return data; } set { data = value; } }
  494.         public object Dereference() { return data; }
  495.     }
  496.  
  497. #if UNITY_EDITOR
  498.     // used to get lists of classes for the ObjectPtr property
  499.     public static class Reflection {
  500.         public static System.Type[] GetTypesInNamespace(string nameSpace, bool includeComponentTypes = false, System.Reflection.Assembly assembly = null) {
  501.             if(assembly == null) {
  502.                 assembly = System.Reflection.Assembly.GetExecutingAssembly();
  503.             }
  504.             System.Type[] types = assembly.GetTypes().Where(t =>
  505.                 System.String.Equals(t.Namespace, nameSpace, System.StringComparison.Ordinal)
  506.                 && (includeComponentTypes || !t.ToString().Contains('+'))).ToArray();
  507.             return types;
  508.         }
  509.         public static string CleanFront(string str, string trimMe) {
  510.             if(str.StartsWith(trimMe)) { return str.Substring(trimMe.Length); }
  511.             return str;
  512.         }
  513.         public static List<string> TypeNamesCleaned(System.Type[] validTypes, string namespaceToClean) {
  514.             List<string> list = new List<string>();
  515.             for(int i = 0; i < validTypes.Length; ++i) {
  516.                 string typename = validTypes[i].ToString();
  517.                 typename = CleanFront(typename, namespaceToClean + ".");
  518.                 list.Add(typename);
  519.             }
  520.             return list;
  521.         }
  522.  
  523.         public static T EditorGUI_EnumPopup<T>(Rect _position, T value) {
  524.             System.Type t = typeof(T);
  525.             if(t.IsEnum) {
  526.                 string[] names = System.Enum.GetNames(t);
  527.                 string thisone = value.ToString();
  528.                 int index = System.Array.IndexOf(names, thisone);
  529.                 index = EditorGUI.Popup(_position, index, names);
  530.                 value = (T)System.Enum.Parse(t, names[index]);
  531.             }
  532.             return value;
  533.         }
  534.     }
  535. #endif
  536. }
  537.  
  538. #if UNITY_EDITOR
  539. // used to create scriptable objects with the ObjectPtr property
  540. public static class ScriptableObjectUtility {
  541.     /// This makes it easy to create, name and place unique new ScriptableObject asset files.
  542.     public static T CreateAsset<T>() where T : ScriptableObject { return CreateAsset(typeof(T)) as T; }
  543.     public static ScriptableObject CreateAsset(System.Type t, string filename = "", string path = "") {
  544.         ScriptableObject asset = ScriptableObject.CreateInstance(t);
  545.         string whereItWasSaved = SaveScriptableObjectAsAsset(asset, filename, path);
  546.         asset = Resources.Load(whereItWasSaved, t) as ScriptableObject;
  547.         return asset;
  548.     }
  549.  
  550.     public static string SaveScriptableObjectAsAsset(ScriptableObject asset, string filename = "", string path = "") {
  551.         System.Type t = asset.GetType();
  552.         if(path == "") {
  553.             path = AssetDatabase.GetAssetPath(Selection.activeObject);
  554.             if(path == "") {
  555.                 path = UnityEngine.SceneManagement.SceneManager.GetActiveScene().path;//"Assets";
  556.                 Debug.Log(path);
  557.                 int idx = path.LastIndexOf("/");
  558.                 if(idx < 0) {
  559.                     path = "Assets";
  560.                 } else {
  561.                     path = path.Substring(0, idx);
  562.                     if(filename == "") {
  563.                         string typename = t.ToString();
  564.                         int idx2 = typename.LastIndexOf(".");
  565.                         if(idx > 0) { typename = typename.Substring(idx2); }
  566.                         filename = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name + typename + ".asset";
  567.                     }
  568.                     Debug.Log(path + " //// " + filename);
  569.                 }
  570.                 //              Debug.Log(UnityEngine.SceneManagement.SceneManager.GetActiveScene().path);
  571.             } else if(System.IO.Path.GetExtension(path) != "") {
  572.                 path = path.Replace(System.IO.Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), "");
  573.             }
  574.         }
  575.         if(filename.Length == 0) { filename = "New " + t.ToString() + ".asset"; }
  576.         string fullpath = path + "/" + filename;
  577.         string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(fullpath);
  578.         AssetDatabase.CreateAsset(asset, assetPathAndName);
  579.         AssetDatabase.SaveAssets();
  580.         AssetDatabase.Refresh();
  581.         EditorUtility.FocusProjectWindow();
  582.         Selection.activeObject = asset;
  583.         Debug.Log("saved " + fullpath);
  584.         return fullpath;
  585.     }
  586. }
  587.  
  588. // enables the ObjectPtr property, not fully utilized by Timer, but certainly utilized by NonStandardAssets
  589. [CustomPropertyDrawer(typeof(NS.ObjectPtr))]
  590. public class PropertyDrawer_ObjectPtr : PropertyDrawer {
  591.     delegate Object SelectNextObjectFunction();
  592.     public static bool showLabel = true;
  593.     public int choice = 0;
  594.     string[] choices_name = { };
  595.     SelectNextObjectFunction[] choices_selectFunc = { };
  596.     Object choicesAreFor;
  597.     System.Type[] possibleResponses;
  598.     string[] cached_typeCreationList_names;
  599.     SelectNextObjectFunction[] cached_TypeCreationList_function;
  600.  
  601.     public static string setToNull = "set to null", delete = "delete";
  602.     public static float defaultOptionWidth = 16, defaultLabelWidth = 48, unitHeight = 16;
  603.     /// <summary>The namespaces to get default selectable classes from</summary>
  604.     protected virtual string[] GetNamespacesForNewComponentOptions() { return null; }
  605.  
  606.     public override float GetPropertyHeight(SerializedProperty _property, GUIContent label) {
  607.         return StandardCalcPropertyHeight();
  608.     }
  609.  
  610.     public static float StandardCalcPropertyHeight() {
  611.         // SerializedProperty asset = _property.FindPropertyRelative("data");
  612.         return unitHeight;//base.GetPropertyHeight (asset, label);
  613.     }
  614.  
  615.     /// <summary>
  616.     /// When the ObjectPtr points to nothing, this method generates the objects that can be created by default
  617.     /// </summary>
  618.     /// <param name="self">Self.</param>
  619.     /// <param name="names">Names.</param>
  620.     /// <param name="functions">Functions.</param>
  621.     private void GenerateTypeCreationList(Component self, out string[] names, out SelectNextObjectFunction[] functions) {
  622.         List<string> list = new List<string>();
  623.         List<SelectNextObjectFunction> list_of_data = new List<SelectNextObjectFunction>();
  624.         string[] theList = GetNamespacesForNewComponentOptions();
  625.         if(theList != null) {
  626.             for(int i = 0; i < theList.Length; ++i) {
  627.                 string namespaceName = theList[i];
  628.                 possibleResponses = NS.Reflection.GetTypesInNamespace(namespaceName);
  629.                 list.AddRange(NS.Reflection.TypeNamesCleaned(possibleResponses, namespaceName));
  630.                 for(int t = 0; t < possibleResponses.Length; t++) {
  631.                     System.Type nextT = possibleResponses[t];
  632.                     list_of_data.Add(() => {
  633.                         return CreateSelectedClass(nextT, self);
  634.                     });
  635.                 }
  636.             }
  637.         }
  638.         list.Insert(0, (theList != null) ? "<-- select Object or create..." : "<-- select Object");
  639.         list_of_data.Insert(0, null);
  640.         names = list.ToArray();
  641.         functions = list_of_data.ToArray();
  642.     }
  643.  
  644.     private void CleanTypename(ref string typename) {
  645.         int lastDot = typename.LastIndexOf('.');
  646.         if(lastDot >= 0) { typename = typename.Substring(lastDot + 1); }
  647.     }
  648.  
  649.     private void GenerateChoicesForSelectedObject(Component self, out string[] names, out SelectNextObjectFunction[] functions) {
  650.         List<string> components = new List<string>();
  651.         List<SelectNextObjectFunction> nextSelectionFunc = new List<SelectNextObjectFunction>();
  652.         string typename = choicesAreFor.GetType().ToString();
  653.         CleanTypename(ref typename);
  654.         components.Add(typename);
  655.         nextSelectionFunc.Add(null);
  656.         GameObject go = choicesAreFor as GameObject;
  657.         bool addSetToNull = true;
  658.         Object addDelete = null;
  659.         if(go != null) {
  660.             Component[] c = go.GetComponents<Component>();
  661.             for(int i = 0; i < c.Length; i++) {
  662.                 Component comp = c[i];
  663.                 if(comp != self) {
  664.                     typename = comp.GetType().ToString();
  665.                     CleanTypename(ref typename);
  666.                     components.Add(typename);
  667.                     nextSelectionFunc.Add(() => { return comp; });
  668.                 }
  669.             }
  670.             addSetToNull = true;
  671.         } else if(choicesAreFor is Component) {
  672.             components.Add(".gameObject");
  673.             GameObject gob = (choicesAreFor as Component).gameObject;
  674.             nextSelectionFunc.Add(() => { return gob; });
  675.             addSetToNull = true;
  676.             addDelete = choicesAreFor;
  677.         }
  678.         if(addSetToNull) {
  679.             components.Add(setToNull);
  680.             nextSelectionFunc.Add(() => {
  681.                 choice = 0; return null;
  682.             });
  683.         }
  684.         if(addDelete != null) {
  685.             components.Add(delete);
  686.             nextSelectionFunc.Add(() => {
  687.                 //Object.DestroyImmediate(addDelete);
  688.                 itemsToCleanup.Add(addDelete);
  689.                 choice = 0; return null;
  690.             });
  691.         }
  692.         names = components.ToArray();
  693.         functions = nextSelectionFunc.ToArray();
  694.     }
  695.  
  696.     [ExecuteInEditMode]
  697.     private class IndirectCleaner : MonoBehaviour {
  698.         public List<Object> itemsToCleanup;
  699.         private void Update() {
  700.             for(int i = itemsToCleanup.Count - 1; i >= 0; --i) {
  701.                 Object o = itemsToCleanup[i];
  702.                 if(o != null) { Object.DestroyImmediate(o); }
  703.             }
  704.             itemsToCleanup.Clear();
  705.             DestroyImmediate(this); // cleaning lady disposes of herself too
  706.         }
  707.     }
  708.     private List<Object> itemsToCleanup = new List<Object>();
  709.     private void RequestCleanup(Component self) {
  710.         // if any items need to be deleted, don't do it now! the UI is in the middle of being drawn!
  711.         // create a separate process that will do it for you
  712.         IndirectCleaner cleaner = self.gameObject.AddComponent<IndirectCleaner>();
  713.         cleaner.itemsToCleanup = this.itemsToCleanup;
  714.     }
  715.  
  716.     /// <summary>called right after an object is assigned</summary>
  717.     public virtual Object FilterImmidiate(Object obj, Component self) {
  718.         return obj;
  719.     }
  720.  
  721.     /// <summary>called right after a new component is created to be assigned</summary>
  722.     protected virtual Object FilterNewComponent(System.Type nextT, Component self, Component newlyCreatedComponent) {
  723.         return newlyCreatedComponent;
  724.     }
  725.  
  726.     /// <summary>called just before UI is finished. This is the last chance to adjust the new setting.</summary>
  727.     public virtual Object FilterFinal(Object newObjToReference, Object prevObj, Component self) {
  728.         return newObjToReference;
  729.     }
  730.  
  731.     private Object CreateSelectedClass(System.Type nextT, Component self) {
  732.         Object obj = null;
  733.         if(self != null && self.gameObject != null) {
  734.             GameObject go = self.gameObject;
  735.             if(nextT.IsSubclassOf(typeof(ScriptableObject))) {
  736.                 obj = ScriptableObjectUtility.CreateAsset(nextT);
  737.             } else {
  738.                 Component newComponent = go.AddComponent(nextT);
  739.                 obj = FilterNewComponent(nextT, self, newComponent);
  740.             }
  741.         }
  742.         return obj;
  743.     }
  744.  
  745.     public static SerializedProperty ObjectPtrAsset(SerializedProperty _property) {
  746.         SerializedProperty asset = _property.FindPropertyRelative("data");
  747.         //asset = asset.FindPropertyRelative("data");
  748.         return asset;
  749.     }
  750.  
  751.     public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label) {
  752.         EditorGUI.BeginProperty(_position, GUIContent.none, _property);
  753.         SerializedProperty asset = ObjectPtrAsset(_property);
  754.         int oldIndent = EditorGUI.indentLevel;
  755.         EditorGUI.indentLevel = 0;
  756.         if(PropertyDrawer_ObjectPtr.showLabel) {
  757.             _position = EditorGUI.PrefixLabel(_position, GUIUtility.GetControlID(FocusType.Passive), _label);
  758.         }
  759.         Component self = _property.serializedObject.targetObject as Component;
  760.         if(asset != null) {
  761.             Object prevObj = asset.objectReferenceValue;
  762.             asset.objectReferenceValue = EditorGUIObjectReference(_position, asset.objectReferenceValue, self);
  763.             asset.objectReferenceValue = FilterFinal(asset.objectReferenceValue, prevObj, self);
  764.             //Contingentable cself = self as Contingentable;
  765.             //if(prevObj != asset.objectReferenceValue && cself != null && cself.ContingencyRecursionCheck() != null) {
  766.             //  Debug.LogWarning("Disallowing recursion of " + asset.objectReferenceValue);
  767.             //  asset.objectReferenceValue = prevObj;
  768.             //}
  769.         }
  770.         EditorGUI.indentLevel = oldIndent;
  771.         EditorGUI.EndProperty();
  772.         if(itemsToCleanup.Count != 0) { RequestCleanup(self); }
  773.     }
  774.  
  775.     // TODO rename this to DoGUI
  776.     public virtual Object EditorGUIObjectReference(Rect _position, Object obj, Component self) {
  777.         int oldIndent = EditorGUI.indentLevel;
  778.         EditorGUI.indentLevel = 0;
  779.         obj = StandardEditorGUIObjectReference(_position, obj, self);
  780.         EditorGUI.indentLevel = oldIndent;
  781.         return obj;
  782.     }
  783.  
  784.     public Object ShowObjectPtrChoicesPopup(Rect _position, Object obj, Component self, bool recalculateChoices) {
  785.         // if the object needs to have it's alternate forms calculated
  786.         if(recalculateChoices || choicesAreFor != obj || choices_name.Length == 0) {
  787.             choicesAreFor = obj;
  788.             // if these choices are for an actual object
  789.             if(choicesAreFor != null) {
  790.                 GenerateChoicesForSelectedObject(self, out choices_name, out choices_selectFunc);
  791.                 choice = 0;
  792.             } else {
  793.                 if(cached_typeCreationList_names == null) {
  794.                     GenerateTypeCreationList(self,
  795.                         out cached_typeCreationList_names, out cached_TypeCreationList_function);
  796.                 }
  797.                 choices_name = cached_typeCreationList_names;
  798.                 choices_selectFunc = cached_TypeCreationList_function;
  799.             }
  800.         }
  801.         // give the alternate options for the object
  802.         int lastChoice = choice;
  803.         _position.x += _position.width;
  804.         _position.width = defaultOptionWidth;
  805.         choice = EditorGUI.Popup(_position, choice, choices_name);
  806.         if(lastChoice != choice) {
  807.             if(choices_selectFunc[choice] != null) {
  808.                 obj = choices_selectFunc[choice]();
  809.             }
  810.         }
  811.         return obj;
  812.     }
  813.  
  814.     public Object StandardEditorGUIObjectReference(Rect _position, Object obj, Component self) {
  815.         float originalWidth = _position.width;
  816.         _position.width = originalWidth - defaultOptionWidth;
  817.         Object prevSelection = obj;
  818.         obj = EditorGUI.ObjectField(_position, obj, typeof(Object), true);
  819.         obj = FilterImmidiate(obj, self);
  820.         obj = ShowObjectPtrChoicesPopup(_position, obj, self, obj != prevSelection);
  821.         return obj;
  822.     }
  823.  
  824.     public Object DoGUIEnumLabeledString<T>(Rect _position, Object obj, Component self,
  825.         ref T enumValue, ref string textValue) {
  826.         int oldindent = EditorGUI.indentLevel;
  827.         EditorGUI.indentLevel = 0;
  828.         Rect r = _position;
  829.         float w = defaultOptionWidth, wl = defaultLabelWidth;
  830.         r.width = wl;
  831.         enumValue = NS.Reflection.EditorGUI_EnumPopup<T>(r, enumValue);
  832.         r.x += r.width;
  833.         r.width = _position.width - w - wl;
  834.         textValue = EditorGUI.TextField(r, textValue);
  835.         obj = ShowObjectPtrChoicesPopup(r, obj, self, true);
  836.         r.x += r.width;
  837.         r.width = w;
  838.         EditorGUI.indentLevel = oldindent;
  839.         return obj;
  840.     }
  841.     public Object DoGUIEnumLabeledObject<T>(Rect _position, Object obj, Component self,
  842.         ref T enumValue, ref Object objectValue) {
  843.         int oldindent = EditorGUI.indentLevel;
  844.         EditorGUI.indentLevel = 0;
  845.         Rect r = _position;
  846.         float w = defaultOptionWidth, wl = defaultLabelWidth;
  847.         r.width = wl;
  848.         enumValue = NS.Reflection.EditorGUI_EnumPopup<T>(r, enumValue);
  849.         r.x += r.width;
  850.         r.width = _position.width - w - wl;
  851.         objectValue = EditorGUI.ObjectField(r, objectValue, typeof(Object), true);
  852.         obj = FilterImmidiate(obj, self);
  853.         obj = ShowObjectPtrChoicesPopup(r, obj, self, true);
  854.         r.x += r.width;
  855.         r.width = w;
  856.         EditorGUI.indentLevel = oldindent;
  857.         return obj;
  858.     }
  859. }
  860. #endif
RAW Paste Data