-
Notifications
You must be signed in to change notification settings - Fork 273
/
TickedVehicle.cs
334 lines (284 loc) · 10.8 KB
/
TickedVehicle.cs
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
#define TRACE_ADJUSTMENTS
using System.Diagnostics;
using TickedPriorityQueue;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace UnitySteer.Behaviors
{
/// <summary>
/// Vehicle subclass oriented towards autonomous bipeds and vehicles, which
/// will be ticked automatically to calculate their direction.
/// </summary>
public abstract class TickedVehicle : Vehicle
{
#region Internal state values
private TickedObject _tickedObject;
private UnityTickedQueue _steeringQueue;
/// <summary>
/// The name of the steering queue for this ticked vehicle.
/// </summary>
[SerializeField] private string _queueName = "Steering";
/// <summary>
/// How often will this Vehicle's steering calculations be ticked.
/// </summary>
[SerializeField] private float _tickLength = 0.1f;
/// <summary>
/// The maximum number of radar update calls processed on the queue per update
/// </summary>
/// <remarks>
/// Notice that this is a limit shared across queue items of the same name, at
/// least until we have some queue settings, so whatever value is set last for
/// the queue will win. Make sure your settings are consistent for objects of
/// the same queue.
/// </remarks>
[SerializeField] private int _maxQueueProcessedPerUpdate = 20;
[SerializeField] private bool _traceAdjustments = false;
#endregion
public CharacterController CharacterController { get; private set; }
/// <summary>
/// Last time the vehicle's tick was completed.
/// </summary>
/// <value>The last tick time.</value>
public float PreviousTickTime { get; private set; }
/// <summary>
/// Current time that the tick was called.
/// </summary>
/// <value>The current tick time.</value>
public float CurrentTickTime { get; private set; }
/// <summary>
/// The time delta between now and when the vehicle's previous tick time and the current one.
/// </summary>
/// <value>The delta time.</value>
public override float DeltaTime
{
get { return CurrentTickTime - PreviousTickTime; }
}
/// <summary>
/// Velocity vector used to orient the agent.
/// </summary>
/// <remarks>
/// This is expected to be set by the subclasses.
/// </remarks>
public Vector3 OrientationVelocity { get; protected set; }
public string QueueName
{
get { return _queueName; }
set { _queueName = value; }
}
/// <summary>
/// Priority queue for this vehicle's updates
/// </summary>
public UnityTickedQueue SteeringQueue
{
get { return _steeringQueue; }
}
/// <summary>
/// Ticked object for the vehicle, so that its owner can configure
/// the priority as desired.
/// </summary>
public TickedObject TickedObject { get; private set; }
#region Unity events
private void Start()
{
CharacterController = GetComponent<CharacterController>();
PreviousTickTime = Time.time;
}
protected override void OnEnable()
{
base.OnEnable();
TickedObject = new TickedObject(OnUpdateSteering);
TickedObject.TickLength = _tickLength;
_steeringQueue = UnityTickedQueue.GetInstance(QueueName);
_steeringQueue.Add(TickedObject);
_steeringQueue.MaxProcessedPerUpdate = _maxQueueProcessedPerUpdate;
}
protected override void OnDisable()
{
DeQueue();
base.OnDisable();
}
#endregion
#region Velocity / Speed methods
private void DeQueue()
{
if (_steeringQueue != null)
{
_steeringQueue.Remove(TickedObject);
}
}
protected void OnUpdateSteering(object obj)
{
if (enabled)
{
// We just calculate the forces, and expect the radar updates itself.
CalculateForces();
}
else
{
/*
* This is an interesting edge case.
*
* Because of the way TickedQueue iterates through its items, we may have
* a case where:
* - The vehicle's OnUpdateSteering is enqueued into the work queue
* - An event previous to it being called causes it to be disabled, and de-queued
* - When the ticked queue gets to it, it executes and re-enqueues it
*
* Therefore we double check that we're not trying to tick it while disabled, and
* if so we de-queue it. Must review TickedQueue to see if there's a way we can
* easily handle these sort of issues without a performance hit.
*/
DeQueue();
// Debug.LogError(string.Format("{0} HOLD YOUR HORSES. Disabled {1} being ticked", Time.time, this));
}
}
protected void CalculateForces()
{
PreviousTickTime = CurrentTickTime;
CurrentTickTime = Time.time;
if (!CanMove || Mathf.Approximately(MaxForce, 0) || Mathf.Approximately(MaxSpeed, 0))
{
return;
}
Profiler.BeginSample("Calculating vehicle forces");
var force = Vector3.zero;
Profiler.BeginSample("Adding up basic steerings");
for (var i = 0; i < Steerings.Length; i++)
{
var s = Steerings[i];
if (s.enabled)
{
force += s.WeighedForce;
}
}
Profiler.EndSample();
LastRawForce = force;
// Enforce speed limit. Steering behaviors are expected to return a
// final desired velocity, not a acceleration, so we apply them directly.
var newVelocity = Vector3.ClampMagnitude(force / Mass, MaxForce);
if (newVelocity.sqrMagnitude == 0)
{
ZeroVelocity();
DesiredVelocity = Vector3.zero;
}
else
{
DesiredVelocity = newVelocity;
}
// Adjusts the velocity by applying the post-processing behaviors.
//
// This currently is not also considering the maximum force, nor
// blending the new velocity into an accumulator. We *could* do that,
// but things are working just fine for now, and it seems like
// overkill.
var adjustedVelocity = Vector3.zero;
Profiler.BeginSample("Adding up post-processing steerings");
for (var i = 0; i < SteeringPostprocessors.Length; i++)
{
var s = SteeringPostprocessors[i];
if (s.enabled)
{
adjustedVelocity += s.WeighedForce;
}
}
Profiler.EndSample();
if (adjustedVelocity != Vector3.zero)
{
adjustedVelocity = Vector3.ClampMagnitude(adjustedVelocity, MaxSpeed);
TraceDisplacement(adjustedVelocity, Color.cyan);
TraceDisplacement(newVelocity, Color.white);
newVelocity = adjustedVelocity;
}
// Update vehicle velocity
SetCalculatedVelocity(newVelocity);
Profiler.EndSample();
}
/// <summary>
/// Applies a steering force to this vehicle
/// </summary>
/// <param name="elapsedTime">
/// How long has elapsed since the last update<see cref="System.Single"/>
/// </param>
private void ApplySteeringForce(float elapsedTime)
{
// Euler integrate (per frame) velocity into position
var acceleration = CalculatePositionDelta(elapsedTime);
acceleration = Vector3.Scale(acceleration, AllowedMovementAxes);
if (CharacterController != null)
{
CharacterController.Move(acceleration);
}
else if (Rigidbody == null || Rigidbody.isKinematic)
{
Transform.position += acceleration;
}
else
{
Rigidbody.MovePosition(Rigidbody.position + acceleration);
}
}
/// <summary>
/// Turns the vehicle towards his velocity vector. Previously called
/// LookTowardsVelocity.
/// </summary>
/// <param name="deltaTime">Time delta to use for turn calculations</param>
protected void AdjustOrientation(float deltaTime)
{
/*
* Avoid adjusting if we aren't applying any velocity. We also
* disregard very small velocities, to avoid jittery movement on
* rounding errors.
*/
if (TargetSpeed > MinSpeedForTurning && Velocity != Vector3.zero)
{
var newForward = Vector3.Scale(OrientationVelocity, AllowedMovementAxes).normalized;
if (TurnTime > 0)
{
newForward = Vector3.Slerp(Transform.forward, newForward, deltaTime / TurnTime);
}
Transform.forward = newForward;
}
}
/// <summary>
/// Records the velocity that was just calculated by CalculateForces in a
/// manner that is specific to each subclass.
/// </summary>
/// <param name="velocity">Newly calculated velocity</param>
protected abstract void SetCalculatedVelocity(Vector3 velocity);
/// <summary>
/// Calculates how much the agent's position should change in a manner that
/// is specific to the vehicle's implementation.
/// </summary>
/// <param name="deltaTime">Time delta to use in position calculations</param>
protected abstract Vector3 CalculatePositionDelta(float deltaTime);
/// <summary>
/// Zeros this vehicle's velocity.
/// </summary>
/// <remarks>
/// Implementation details are left up to the subclasses.
/// </remarks>
protected abstract void ZeroVelocity();
#endregion
private void Update()
{
if (CanMove)
{
ApplySteeringForce(Time.deltaTime);
AdjustOrientation(Time.deltaTime);
}
}
[Conditional("TRACE_ADJUSTMENTS")]
private void TraceDisplacement(Vector3 delta, Color color)
{
if (_traceAdjustments)
{
Debug.DrawLine(Transform.position, Transform.position + delta, color);
}
}
public void Stop()
{
CanMove = false;
ZeroVelocity();
}
}
}