File size: 6,762 Bytes
18a519f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | using System;
using UnityEngine;
namespace Unity.VisualScripting
{
/// <summary>
/// Runs a cooldown timer to throttle flow and outputs remaining measurements.
/// </summary>
[UnitCategory("Time")]
[TypeIcon(typeof(Timer))]
[UnitOrder(8)]
public sealed class Cooldown : Unit, IGraphElementWithData, IGraphEventListener
{
public sealed class Data : IGraphElementData
{
public float remaining;
public float duration;
public bool unscaled;
public bool isReady => remaining <= 0;
public Delegate update;
public bool isListening;
}
/// <summary>
/// The moment at which to try using the cooldown.
/// </summary>
[DoNotSerialize]
[PortLabelHidden]
public ControlInput enter { get; private set; }
/// <summary>
/// Trigger to force reset the cooldown.
/// </summary>
[DoNotSerialize]
public ControlInput reset { get; private set; }
/// <summary>
/// The total duration of the cooldown.
/// </summary>
[DoNotSerialize]
public ValueInput duration { get; private set; }
/// <summary>
/// Whether to ignore the time scale.
/// </summary>
[DoNotSerialize]
[PortLabel("Unscaled")]
public ValueInput unscaledTime { get; private set; }
/// <summary>
/// Called upon entry when the cooldown is ready.
/// </summary>
[DoNotSerialize]
[PortLabel("Ready")]
public ControlOutput exitReady { get; private set; }
/// <summary>
/// Called upon entry when the cooldown is not yet ready.
/// </summary>
[DoNotSerialize]
[PortLabel("Not Ready")]
public ControlOutput exitNotReady { get; private set; }
/// <summary>
/// Called each frame while the cooldown timer is active.
/// </summary>
[DoNotSerialize]
public ControlOutput tick { get; private set; }
/// <summary>
/// Called when the cooldown timer reaches zero.
/// </summary>
[DoNotSerialize]
[PortLabel("Completed")]
public ControlOutput becameReady { get; private set; }
/// <summary>
/// The number of seconds remaining until the cooldown is ready.
/// </summary>
[DoNotSerialize]
[PortLabel("Remaining")]
public ValueOutput remainingSeconds { get; private set; }
/// <summary>
/// The proportion of the duration remaining until the cooldown is ready (0-1).
/// </summary>
[DoNotSerialize]
[PortLabel("Remaining %")]
public ValueOutput remainingRatio { get; private set; }
protected override void Definition()
{
enter = ControlInput(nameof(enter), Enter);
reset = ControlInput(nameof(reset), Reset);
duration = ValueInput(nameof(duration), 1f);
unscaledTime = ValueInput(nameof(unscaledTime), false);
exitReady = ControlOutput(nameof(exitReady));
exitNotReady = ControlOutput(nameof(exitNotReady));
tick = ControlOutput(nameof(tick));
becameReady = ControlOutput(nameof(becameReady));
remainingSeconds = ValueOutput<float>(nameof(remainingSeconds));
remainingRatio = ValueOutput<float>(nameof(remainingRatio));
Requirement(duration, enter);
Requirement(unscaledTime, enter);
Succession(enter, exitReady);
Succession(enter, exitNotReady);
Succession(enter, tick);
Succession(enter, becameReady);
Assignment(enter, remainingSeconds);
Assignment(enter, remainingRatio);
}
public IGraphElementData CreateData()
{
return new Data();
}
public void StartListening(GraphStack stack)
{
var data = stack.GetElementData<Data>(this);
if (data.isListening)
{
return;
}
var reference = stack.ToReference();
var hook = new EventHook(EventHooks.Update, stack.machine);
Action<EmptyEventArgs> update = args => TriggerUpdate(reference);
EventBus.Register(hook, update);
data.update = update;
data.isListening = true;
}
public void StopListening(GraphStack stack)
{
var data = stack.GetElementData<Data>(this);
if (!data.isListening)
{
return;
}
var hook = new EventHook(EventHooks.Update, stack.machine);
EventBus.Unregister(hook, data.update);
data.update = null;
data.isListening = false;
}
public bool IsListening(GraphPointer pointer)
{
return pointer.GetElementData<Data>(this).isListening;
}
private void TriggerUpdate(GraphReference reference)
{
using (var flow = Flow.New(reference))
{
Update(flow);
}
}
private ControlOutput Enter(Flow flow)
{
var data = flow.stack.GetElementData<Data>(this);
if (data.isReady)
{
return Reset(flow);
}
else
{
return exitNotReady;
}
}
private ControlOutput Reset(Flow flow)
{
var data = flow.stack.GetElementData<Data>(this);
data.duration = flow.GetValue<float>(duration);
data.remaining = data.duration;
data.unscaled = flow.GetValue<bool>(unscaledTime);
return exitReady;
}
private void AssignMetrics(Flow flow, Data data)
{
flow.SetValue(remainingSeconds, data.remaining);
flow.SetValue(remainingRatio, Mathf.Clamp01(data.remaining / data.duration));
}
public void Update(Flow flow)
{
var data = flow.stack.GetElementData<Data>(this);
if (data.isReady)
{
return;
}
data.remaining -= data.unscaled ? Time.unscaledDeltaTime : Time.deltaTime;
data.remaining = Mathf.Max(0f, data.remaining);
AssignMetrics(flow, data);
var stack = flow.PreserveStack();
flow.Invoke(tick);
if (data.isReady)
{
flow.RestoreStack(stack);
flow.Invoke(becameReady);
}
flow.DisposePreservedStack(stack);
}
}
}
|