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}