A game about forced loneliness, made by TACStudios
1using System; 2using UnityEngine; 3 4namespace Unity.VisualScripting 5{ 6 /// <summary> 7 /// Runs a timer and outputs elapsed and remaining measurements. 8 /// </summary> 9 [UnitCategory("Time")] 10 [UnitOrder(7)] 11 public sealed class Timer : Unit, IGraphElementWithData, IGraphEventListener 12 { 13 public sealed class Data : IGraphElementData 14 { 15 public float elapsed; 16 17 public float duration; 18 19 public bool active; 20 21 public bool paused; 22 23 public bool unscaled; 24 25 public Delegate update; 26 27 public bool isListening; 28 } 29 30 /// <summary> 31 /// The moment at which to start the timer. 32 /// If the timer is already started, this will reset it. 33 /// If the timer is paused, this will resume it. 34 /// </summary> 35 [DoNotSerialize] 36 public ControlInput start { get; private set; } 37 38 /// <summary> 39 /// Trigger to pause the timer. 40 /// </summary> 41 [DoNotSerialize] 42 public ControlInput pause { get; private set; } 43 44 /// <summary> 45 /// Trigger to resume the timer. 46 /// </summary> 47 [DoNotSerialize] 48 public ControlInput resume { get; private set; } 49 50 /// <summary> 51 /// Trigger to toggle the timer. 52 /// If it is idle, it will start. 53 /// If it is active, it will pause. 54 /// If it is paused, it will resume. 55 /// </summary> 56 [DoNotSerialize] 57 public ControlInput toggle { get; private set; } 58 59 /// <summary> 60 /// The total duration of the timer. 61 /// </summary> 62 [DoNotSerialize] 63 public ValueInput duration { get; private set; } 64 65 /// <summary> 66 /// Whether to ignore the time scale. 67 /// </summary> 68 [DoNotSerialize] 69 [PortLabel("Unscaled")] 70 public ValueInput unscaledTime { get; private set; } 71 72 /// <summary> 73 /// Called when the timer is started.co 74 /// </summary> 75 [DoNotSerialize] 76 public ControlOutput started { get; private set; } 77 78 /// <summary> 79 /// Called each frame while the timer is active. 80 /// </summary> 81 [DoNotSerialize] 82 public ControlOutput tick { get; private set; } 83 84 /// <summary> 85 /// Called when the timer completes. 86 /// </summary> 87 [DoNotSerialize] 88 public ControlOutput completed { get; private set; } 89 90 /// <summary> 91 /// The number of seconds elapsed since the timer started. 92 /// </summary> 93 [DoNotSerialize] 94 [PortLabel("Elapsed")] 95 public ValueOutput elapsedSeconds { get; private set; } 96 97 /// <summary> 98 /// The proportion of the duration that has elapsed (0-1). 99 /// </summary> 100 [DoNotSerialize] 101 [PortLabel("Elapsed %")] 102 public ValueOutput elapsedRatio { get; private set; } 103 104 /// <summary> 105 /// The number of seconds remaining until the timer is elapsed. 106 /// </summary> 107 [DoNotSerialize] 108 [PortLabel("Remaining")] 109 public ValueOutput remainingSeconds { get; private set; } 110 111 /// <summary> 112 /// The proportion of the duration remaining until the timer is elapsed (0-1). 113 /// </summary> 114 [DoNotSerialize] 115 [PortLabel("Remaining %")] 116 public ValueOutput remainingRatio { get; private set; } 117 118 protected override void Definition() 119 { 120 isControlRoot = true; 121 122 start = ControlInput(nameof(start), Start); 123 pause = ControlInput(nameof(pause), Pause); 124 resume = ControlInput(nameof(resume), Resume); 125 toggle = ControlInput(nameof(toggle), Toggle); 126 127 duration = ValueInput(nameof(duration), 1f); 128 unscaledTime = ValueInput(nameof(unscaledTime), false); 129 130 started = ControlOutput(nameof(started)); 131 tick = ControlOutput(nameof(tick)); 132 completed = ControlOutput(nameof(completed)); 133 134 elapsedSeconds = ValueOutput<float>(nameof(elapsedSeconds)); 135 elapsedRatio = ValueOutput<float>(nameof(elapsedRatio)); 136 137 remainingSeconds = ValueOutput<float>(nameof(remainingSeconds)); 138 remainingRatio = ValueOutput<float>(nameof(remainingRatio)); 139 } 140 141 public IGraphElementData CreateData() 142 { 143 return new Data(); 144 } 145 146 public void StartListening(GraphStack stack) 147 { 148 var data = stack.GetElementData<Data>(this); 149 150 if (data.isListening) 151 { 152 return; 153 } 154 155 var reference = stack.ToReference(); 156 var hook = new EventHook(EventHooks.Update, stack.machine); 157 Action<EmptyEventArgs> update = args => TriggerUpdate(reference); 158 EventBus.Register(hook, update); 159 data.update = update; 160 data.isListening = true; 161 } 162 163 public void StopListening(GraphStack stack) 164 { 165 var data = stack.GetElementData<Data>(this); 166 167 if (!data.isListening) 168 { 169 return; 170 } 171 172 var hook = new EventHook(EventHooks.Update, stack.machine); 173 EventBus.Unregister(hook, data.update); 174 175 stack.ClearReference(); 176 177 data.update = null; 178 data.isListening = false; 179 } 180 181 public bool IsListening(GraphPointer pointer) 182 { 183 return pointer.GetElementData<Data>(this).isListening; 184 } 185 186 private void TriggerUpdate(GraphReference reference) 187 { 188 using (var flow = Flow.New(reference)) 189 { 190 Update(flow); 191 } 192 } 193 194 private ControlOutput Start(Flow flow) 195 { 196 var data = flow.stack.GetElementData<Data>(this); 197 198 data.elapsed = 0; 199 data.duration = flow.GetValue<float>(duration); 200 data.active = true; 201 data.paused = false; 202 data.unscaled = flow.GetValue<bool>(unscaledTime); 203 204 AssignMetrics(flow, data); 205 206 return started; 207 } 208 209 private ControlOutput Pause(Flow flow) 210 { 211 var data = flow.stack.GetElementData<Data>(this); 212 213 data.paused = true; 214 215 return null; 216 } 217 218 private ControlOutput Resume(Flow flow) 219 { 220 var data = flow.stack.GetElementData<Data>(this); 221 222 data.paused = false; 223 224 return null; 225 } 226 227 private ControlOutput Toggle(Flow flow) 228 { 229 var data = flow.stack.GetElementData<Data>(this); 230 231 if (!data.active) 232 { 233 return Start(flow); 234 } 235 else 236 { 237 data.paused = !data.paused; 238 239 return null; 240 } 241 } 242 243 private void AssignMetrics(Flow flow, Data data) 244 { 245 flow.SetValue(elapsedSeconds, data.elapsed); 246 flow.SetValue(elapsedRatio, Mathf.Clamp01(data.elapsed / data.duration)); 247 248 flow.SetValue(remainingSeconds, Mathf.Max(0, data.duration - data.elapsed)); 249 flow.SetValue(remainingRatio, Mathf.Clamp01((data.duration - data.elapsed) / data.duration)); 250 } 251 252 public void Update(Flow flow) 253 { 254 var data = flow.stack.GetElementData<Data>(this); 255 256 if (!data.active || data.paused) 257 { 258 return; 259 } 260 261 data.elapsed += data.unscaled ? Time.unscaledDeltaTime : Time.deltaTime; 262 263 data.elapsed = Mathf.Min(data.elapsed, data.duration); 264 265 AssignMetrics(flow, data); 266 267 var stack = flow.PreserveStack(); 268 269 flow.Invoke(tick); 270 271 if (data.elapsed >= data.duration) 272 { 273 data.active = false; 274 275 flow.RestoreStack(stack); 276 277 flow.Invoke(completed); 278 } 279 280 flow.DisposePreservedStack(stack); 281 } 282 } 283}