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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
|
#if MODULE_ENTITIES
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
namespace Pathfinding.ECS {
using Pathfinding;
using Pathfinding.Util;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
/// <summary>
/// Holds unmanaged information about an off-mesh link that the agent is currently traversing.
/// This component is added to the agent when it starts traversing an off-mesh link.
/// It is removed when the agent has finished traversing the link.
///
/// See: <see cref="ManagedAgentOffMeshLinkTraversal"/>
/// </summary>
public struct AgentOffMeshLinkTraversal : IComponentData {
/// <summary>\copydocref{OffMeshLinks.OffMeshLinkTracer.relativeStart}</summary>
public float3 relativeStart;
/// <summary>\copydocref{OffMeshLinks.OffMeshLinkTracer.relativeEnd}</summary>
public float3 relativeEnd;
/// <summary>\copydocref{OffMeshLinks.OffMeshLinkTracer.relativeStart}. Deprecated: Use relativeStart instead</summary>
[System.Obsolete("Use relativeStart instead")]
public float3 firstPosition => relativeStart;
/// <summary>\copydocref{OffMeshLinks.OffMeshLinkTracer.relativeEnd}. Deprecated: Use relativeEnd instead</summary>
[System.Obsolete("Use relativeEnd instead")]
public float3 secondPosition => relativeEnd;
/// <summary>\copydocref{OffMeshLinks.OffMeshLinkTracer.isReverse}</summary>
public bool isReverse;
public AgentOffMeshLinkTraversal (OffMeshLinks.OffMeshLinkTracer linkInfo) {
relativeStart = linkInfo.relativeStart;
relativeEnd = linkInfo.relativeEnd;
isReverse = linkInfo.isReverse;
}
}
/// <summary>
/// Holds managed information about an off-mesh link that the agent is currently traversing.
/// This component is added to the agent when it starts traversing an off-mesh link.
/// It is removed when the agent has finished traversing the link.
///
/// See: <see cref="AgentOffMeshLinkTraversal"/>
/// </summary>
public class ManagedAgentOffMeshLinkTraversal : IComponentData, System.ICloneable, ICleanupComponentData {
/// <summary>Internal context used to pass component data to the coroutine</summary>
public AgentOffMeshLinkTraversalContext context;
/// <summary>Coroutine which is used to traverse the link</summary>
public System.Collections.IEnumerator coroutine;
public IOffMeshLinkHandler handler;
public IOffMeshLinkStateMachine stateMachine;
public ManagedAgentOffMeshLinkTraversal() {}
public ManagedAgentOffMeshLinkTraversal (AgentOffMeshLinkTraversalContext context, IOffMeshLinkHandler handler) {
this.context = context;
this.handler = handler;
this.coroutine = null;
this.stateMachine = null;
}
public object Clone () {
// This will set coroutine and stateMachine to null.
// This is correct, as the coroutine cannot be cloned, and the state machine may be unique for a specific agent
return new ManagedAgentOffMeshLinkTraversal((AgentOffMeshLinkTraversalContext)context.Clone(), handler);
}
}
public struct MovementTarget {
internal bool isReached;
public bool reached => isReached;
public MovementTarget (bool isReached) {
this.isReached = isReached;
}
}
/// <summary>
/// Context with helpers for traversing an off-mesh link.
///
/// This will be passed to the code that is responsible for traversing the off-mesh link.
///
/// Warning: This context should never be accessed outside of an implementation of the <see cref="IOffMeshLinkStateMachine"/> interface.
/// </summary>
public class AgentOffMeshLinkTraversalContext : System.ICloneable {
internal unsafe AgentOffMeshLinkTraversal* linkInfoPtr;
internal unsafe MovementControl* movementControlPtr;
internal unsafe MovementSettings* movementSettingsPtr;
internal unsafe LocalTransform* transformPtr;
internal unsafe AgentMovementPlane* movementPlanePtr;
/// <summary>The entity that is traversing the off-mesh link</summary>
public Entity entity;
/// <summary>Some internal state of the agent</summary>
[Unity.Properties.DontCreateProperty]
public ManagedState managedState;
/// <summary>
/// The off-mesh link that is being traversed.
///
/// See: <see cref="link"/>
/// </summary>
[Unity.Properties.DontCreateProperty]
internal OffMeshLinks.OffMeshLinkConcrete concreteLink;
protected bool disabledRVO;
protected float backupRotationSmoothing = float.NaN;
/// <summary>
/// Delta time since the last link simulation.
///
/// During high time scales, the simulation may run multiple substeps per frame.
///
/// This is not the same as Time.deltaTime. Inside the link coroutine, you should always use this field instead of Time.deltaTime.
/// </summary>
public float deltaTime;
protected GameObject gameObjectCache;
/// <summary>
/// GameObject associated with the agent.
///
/// In most cases, an agent is associated with an agent, but this is not always the case.
/// For example, if you have created an entity without using the <see cref="FollowerEntity"/> component, this property may return null.
///
/// Note: When directly modifying the agent's transform during a link traversal, you should use the <see cref="transform"/> property instead of modifying the GameObject's transform.
/// </summary>
public virtual GameObject gameObject {
get {
if (gameObjectCache == null) {
var follower = BatchedEvents.Find<FollowerEntity, Entity>(entity, (follower, entity) => follower.entity == entity);
if (follower != null) gameObjectCache = follower.gameObject;
}
return gameObjectCache;
}
}
/// <summary>ECS LocalTransform component attached to the agent</summary>
public ref LocalTransform transform {
get {
unsafe {
return ref *transformPtr;
}
}
}
/// <summary>The movement settings for the agent</summary>
public ref MovementSettings movementSettings {
get {
unsafe {
return ref *movementSettingsPtr;
}
}
}
/// <summary>
/// How the agent should move.
///
/// The agent will move according to this data, every frame.
/// </summary>
public ref MovementControl movementControl {
get {
unsafe {
return ref *movementControlPtr;
}
}
}
/// <summary>Information about the off-mesh link that the agent is traversing</summary>
public OffMeshLinks.OffMeshLinkTracer link {
get {
unsafe {
return new OffMeshLinks.OffMeshLinkTracer(concreteLink, linkInfoPtr->relativeStart, linkInfoPtr->relativeEnd, linkInfoPtr->isReverse);
}
}
}
/// <summary>
/// Information about the off-mesh link that the agent is traversing.
///
/// Deprecated: Use the <see cref="link"/> property instead
/// </summary>
[System.Obsolete("Use the link property instead")]
public AgentOffMeshLinkTraversal linkInfo {
get {
unsafe {
return *linkInfoPtr;
}
}
}
/// <summary>
/// The plane in which the agent is moving.
///
/// In a 3D game, this will typically be the XZ plane, but in a 2D game
/// it will typically be the XY plane. Games on spherical planets could have planes that are aligned with the surface of the planet.
/// </summary>
public ref NativeMovementPlane movementPlane {
get {
unsafe {
return ref movementPlanePtr->value;
}
}
}
public AgentOffMeshLinkTraversalContext (OffMeshLinks.OffMeshLinkConcrete link) {
this.concreteLink = link;
}
/// <summary>
/// Internal method to set the data of the context.
///
/// This is used by the job system to set the data of the context.
/// You should almost never need to use this.
/// </summary>
public virtual unsafe void SetInternalData (Entity entity, ref LocalTransform transform, ref AgentMovementPlane movementPlane, ref MovementControl movementControl, ref MovementSettings movementSettings, ref AgentOffMeshLinkTraversal linkInfo, ManagedState state, float deltaTime) {
this.linkInfoPtr = (AgentOffMeshLinkTraversal*)UnsafeUtility.AddressOf(ref linkInfo);
this.movementControlPtr = (MovementControl*)UnsafeUtility.AddressOf(ref movementControl);
this.movementSettingsPtr = (MovementSettings*)UnsafeUtility.AddressOf(ref movementSettings);
this.transformPtr = (LocalTransform*)UnsafeUtility.AddressOf(ref transform);
this.movementPlanePtr = (AgentMovementPlane*)UnsafeUtility.AddressOf(ref movementPlane);
this.managedState = state;
this.deltaTime = deltaTime;
this.entity = entity;
}
/// <summary>
/// Disables local avoidance for the agent.
///
/// Agents that traverse links are already marked as 'unstoppable' by the local avoidance system,
/// but calling this method will make other agents ignore them completely while traversing the link.
/// </summary>
public void DisableLocalAvoidance () {
if (managedState.enableLocalAvoidance) {
disabledRVO = true;
managedState.enableLocalAvoidance = false;
}
}
/// <summary>
/// Disables rotation smoothing for the agent.
///
/// This disables the effect of <see cref="MovementSettings.rotationSmoothing"/> while the agent is traversing the link.
/// Having rotation smoothing enabled can make the agent rotate towards its target rotation more slowly,
/// which is sometimes not desirable.
///
/// Rotation smoothing will automatically be restored when the agent finishes traversing the link (if it was enabled before).
///
/// The <see cref="MoveTowards"/> method automatically disables rotation smoothing when called.
/// </summary>
public void DisableRotationSmoothing () {
if (float.IsNaN(backupRotationSmoothing) && movementSettings.rotationSmoothing > 0) {
backupRotationSmoothing = movementSettings.rotationSmoothing;
movementSettings.rotationSmoothing = 0;
}
}
/// <summary>
/// Restores the agent's settings to what it was before the link traversal started.
///
/// This undos the changes made by <see cref="DisableLocalAvoidance"/> and <see cref="DisableRotationSmoothing"/>.
///
/// This method is automatically called when the agent finishes traversing the link.
/// </summary>
public virtual void Restore () {
if (disabledRVO) {
managedState.enableLocalAvoidance = true;
disabledRVO = false;
}
if (!float.IsNaN(backupRotationSmoothing)) {
movementSettings.rotationSmoothing = backupRotationSmoothing;
backupRotationSmoothing = float.NaN;
}
}
/// <summary>Teleports the agent to the given position</summary>
public virtual void Teleport (float3 position) {
transform.Position = position;
}
/// <summary>
/// Thrown when the off-mesh link traversal should be aborted.
///
/// See: <see cref="AgentOffMeshLinkTraversal.Abort"/>
/// </summary>
public class AbortOffMeshLinkTraversal : System.Exception {}
/// <summary>
/// Aborts traversing the off-mesh link.
///
/// This will immediately stop your off-mesh link traversal coroutine.
///
/// This is useful if your agent was traversing an off-mesh link, but you have detected that it cannot continue.
/// Maybe the ladder it was climbing was destroyed, or the bridge it was walking on collapsed.
///
/// Note: If you instead want to immediately make the agent move to the end of the link, you can call <see cref="Teleport"/>, and then use 'yield break;' from your coroutine.
/// </summary>
/// <param name="teleportToStart">If true, the agent will be teleported back to the start of the link (from the perspective of the agent). Its rotation will remain unchanged.</param>
public virtual void Abort (bool teleportToStart = true) {
if (teleportToStart) Teleport(link.relativeStart);
// Cancel the current path, as otherwise the agent will instantly try to traverse the off-mesh link again.
managedState.pathTracer.SetFromSingleNode(managedState.pathTracer.startNode, transform.Position, movementPlane);
throw new AbortOffMeshLinkTraversal();
}
/// <summary>
/// Move towards a point while ignoring the navmesh.
/// This method should be called repeatedly until the returned <see cref="MovementTarget.reached"/> property is true.
///
/// Returns: A <see cref="MovementTarget"/> struct which can be used to check if the target has been reached.
///
/// Note: This method completely ignores the navmesh. It also overrides local avoidance, if enabled (other agents will still avoid it, but this agent will not avoid other agents).
///
/// TODO: The gravity property is not yet implemented. Gravity is always applied.
/// </summary>
/// <param name="position">The position to move towards.</param>
/// <param name="rotation">The rotation to rotate towards.</param>
/// <param name="gravity">If true, gravity will be applied to the agent.</param>
/// <param name="slowdown">If true, the agent will slow down as it approaches the target.</param>
public virtual MovementTarget MoveTowards (float3 position, quaternion rotation, bool gravity, bool slowdown) {
// If rotation smoothing was enabled, it could cause a very slow convergence to the target rotation.
// Therefore, we disable it here.
// The agent will try to remove its remaining rotation smoothing offset as quickly as possible.
// After the off-mesh link is traversed, the rotation smoothing will be automatically restored.
DisableRotationSmoothing();
var dirInPlane = movementPlane.ToPlane(position - transform.Position);
var remainingDistance = math.length(dirInPlane);
var maxSpeed = movementSettings.follower.Speed(slowdown ? remainingDistance : float.PositiveInfinity);
var speed = movementSettings.follower.Accelerate(movementControl.speed, movementSettings.follower.slowdownTime, deltaTime);
speed = math.min(speed, maxSpeed);
var targetRot = movementPlane.ToPlane(rotation);
var currentRot = movementPlane.ToPlane(transform.Rotation);
var remainingRot = Mathf.Abs(AstarMath.DeltaAngle(currentRot, targetRot));
movementControl = new MovementControl {
targetPoint = position,
endOfPath = position,
speed = speed,
maxSpeed = speed * 1.1f,
hierarchicalNodeIndex = -1,
overrideLocalAvoidance = true,
targetRotation = targetRot,
targetRotationHint = targetRot,
targetRotationOffset = 0,
rotationSpeed = math.radians(movementSettings.follower.rotationSpeed),
};
return new MovementTarget {
isReached = remainingDistance <= (slowdown ? 0.01f : speed * (1/30f)) && remainingRot < math.radians(1),
};
}
public virtual object Clone () {
var clone = (AgentOffMeshLinkTraversalContext)MemberwiseClone();
clone.entity = Entity.Null;
clone.gameObjectCache = null;
clone.managedState = null;
unsafe {
linkInfoPtr = null;
movementControlPtr = null;
movementSettingsPtr = null;
transformPtr = null;
movementPlanePtr = null;
}
return clone;
}
}
}
// ctx.MoveTowards (position, rotation, rvo = Auto | Disabled | AutoUnstoppable, gravity = auto|disabled) -> { reached() }
// MovementTarget { ... }
// while (!movementTarget.reached) {
// ctx.SetMovementTarget(movementTarget);
// yield return null;
// }
// yield return ctx.MoveTo(position, rotation)
// ctx.TeleportTo(position, rotation)
#endif
|