SHARE
TWEET

Timer.cs

mvaganov Oct 24th, 2017 (edited) 210 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
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top