dacha 0.18.0-alpha.8 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/build/contrib/components/index.d.ts +1 -0
  2. package/build/contrib/components/index.js +1 -0
  3. package/build/contrib/components/interpolation/index.d.ts +85 -0
  4. package/build/contrib/components/interpolation/index.js +101 -0
  5. package/build/contrib/components/rigid-body/index.d.ts +108 -14
  6. package/build/contrib/components/rigid-body/index.js +191 -22
  7. package/build/contrib/systems/animator/index.d.ts +3 -2
  8. package/build/contrib/systems/animator/index.js +6 -3
  9. package/build/contrib/systems/behavior-system/system.d.ts +4 -2
  10. package/build/contrib/systems/behavior-system/system.js +11 -3
  11. package/build/contrib/systems/behavior-system/types.d.ts +7 -10
  12. package/build/contrib/systems/character-controller/index.d.ts +4 -2
  13. package/build/contrib/systems/character-controller/index.js +22 -17
  14. package/build/contrib/systems/character-controller/one-way-validator.d.ts +15 -0
  15. package/build/contrib/systems/character-controller/one-way-validator.js +73 -0
  16. package/build/contrib/systems/game-stats-meter/index.d.ts +4 -3
  17. package/build/contrib/systems/game-stats-meter/index.js +11 -10
  18. package/build/contrib/systems/index.d.ts +2 -0
  19. package/build/contrib/systems/index.js +1 -0
  20. package/build/contrib/systems/interpolator/api.d.ts +40 -0
  21. package/build/contrib/systems/interpolator/api.js +70 -0
  22. package/build/contrib/systems/interpolator/index.d.ts +3 -0
  23. package/build/contrib/systems/interpolator/index.js +2 -0
  24. package/build/contrib/systems/interpolator/system.d.ts +31 -0
  25. package/build/contrib/systems/interpolator/system.js +96 -0
  26. package/build/contrib/systems/interpolator/tests/helpers.d.ts +13 -0
  27. package/build/contrib/systems/interpolator/tests/helpers.js +45 -0
  28. package/build/contrib/systems/interpolator/utils.d.ts +5 -0
  29. package/build/contrib/systems/interpolator/utils.js +41 -0
  30. package/build/contrib/systems/physics-system/api.d.ts +45 -1
  31. package/build/contrib/systems/physics-system/api.js +49 -0
  32. package/build/contrib/systems/physics-system/consts.d.ts +13 -0
  33. package/build/contrib/systems/physics-system/consts.js +17 -0
  34. package/build/contrib/systems/physics-system/physics-system.d.ts +2 -2
  35. package/build/contrib/systems/physics-system/physics-system.js +24 -4
  36. package/build/contrib/systems/physics-system/subsystems/collision-detection/dynamic-aabb-tree/index.d.ts +1 -1
  37. package/build/contrib/systems/physics-system/subsystems/collision-detection/dynamic-aabb-tree/index.js +21 -15
  38. package/build/contrib/systems/physics-system/subsystems/collision-detection/index.d.ts +11 -11
  39. package/build/contrib/systems/physics-system/subsystems/collision-detection/index.js +133 -125
  40. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/check-boxes-intersection.js +5 -3
  41. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.d.ts +1 -0
  42. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.js +2 -1
  43. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-capsule/check-box-and-capsule-intersection.js +4 -1
  44. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-segment/check-box-and-segment-intersection.js +4 -2
  45. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/points.d.ts +2 -0
  46. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/points.js +16 -0
  47. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/index.js +2 -0
  48. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/tests/helpers.js +3 -14
  49. package/build/contrib/systems/physics-system/subsystems/collision-detection/query-utils.d.ts +5 -0
  50. package/build/contrib/systems/physics-system/subsystems/collision-detection/query-utils.js +97 -81
  51. package/build/contrib/systems/physics-system/subsystems/collision-detection/shape-cast-checkers/tests/helpers.js +2 -7
  52. package/build/contrib/systems/physics-system/subsystems/collision-detection/tests/assertions.d.ts +1 -0
  53. package/build/contrib/systems/physics-system/subsystems/collision-detection/tests/assertions.js +7 -0
  54. package/build/contrib/systems/physics-system/subsystems/collision-detection/types.d.ts +0 -15
  55. package/build/contrib/systems/physics-system/subsystems/collision-detection/utils.d.ts +3 -0
  56. package/build/contrib/systems/physics-system/subsystems/collision-detection/utils.js +9 -0
  57. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-state-manager.d.ts +63 -0
  58. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-state-manager.js +170 -0
  59. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-utils.d.ts +5 -0
  60. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-utils.js +41 -0
  61. package/build/contrib/systems/physics-system/subsystems/constraint-solver/impulse-utils.d.ts +16 -0
  62. package/build/contrib/systems/physics-system/subsystems/constraint-solver/impulse-utils.js +77 -0
  63. package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.d.ts +53 -4
  64. package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.js +334 -116
  65. package/build/contrib/systems/physics-system/subsystems/constraint-solver/one-way-validator.d.ts +11 -0
  66. package/build/contrib/{utils → systems/physics-system/subsystems/constraint-solver}/one-way-validator.js +22 -25
  67. package/build/contrib/systems/physics-system/subsystems/constraint-solver/sleep-support-tracker.d.ts +13 -0
  68. package/build/contrib/systems/physics-system/subsystems/constraint-solver/sleep-support-tracker.js +42 -0
  69. package/build/contrib/systems/physics-system/subsystems/constraint-solver/tests/helpers.d.ts +4 -0
  70. package/build/contrib/systems/physics-system/subsystems/constraint-solver/tests/helpers.js +20 -0
  71. package/build/contrib/systems/physics-system/subsystems/physics/index.d.ts +15 -4
  72. package/build/contrib/systems/physics-system/subsystems/physics/index.js +131 -53
  73. package/build/contrib/systems/physics-system/subsystems/physics/mass-properties.d.ts +2 -0
  74. package/build/contrib/systems/physics-system/subsystems/physics/mass-properties.js +55 -0
  75. package/build/contrib/systems/physics-system/tests/helpers.d.ts +5 -2
  76. package/build/contrib/systems/physics-system/tests/helpers.js +11 -2
  77. package/build/contrib/systems/physics-system/types.d.ts +18 -2
  78. package/build/contrib/systems/renderer/actor-render-tree.js +15 -6
  79. package/build/contrib/systems/renderer/filters/index.d.ts +1 -1
  80. package/build/contrib/systems/renderer/filters/index.js +1 -1
  81. package/build/contrib/systems/renderer/material/index.d.ts +2 -1
  82. package/build/contrib/systems/renderer/material/index.js +1 -1
  83. package/build/contrib/systems/renderer/renderer.d.ts +2 -2
  84. package/build/contrib/systems/renderer/renderer.js +3 -4
  85. package/build/contrib/systems/renderer/types.d.ts +0 -3
  86. package/build/engine/data-lib/index.d.ts +1 -1
  87. package/build/engine/data-lib/index.js +1 -1
  88. package/build/engine/data-lib/pool.d.ts +9 -0
  89. package/build/engine/data-lib/pool.js +19 -0
  90. package/build/engine/engine.js +4 -1
  91. package/build/engine/game-loop.d.ts +3 -1
  92. package/build/engine/game-loop.js +11 -8
  93. package/build/engine/math-lib/math/ops.d.ts +16 -0
  94. package/build/engine/math-lib/math/ops.js +25 -0
  95. package/build/engine/scene/scene-manager.d.ts +4 -1
  96. package/build/engine/scene/scene-manager.js +5 -1
  97. package/build/engine/system/index.d.ts +1 -1
  98. package/build/engine/system/system.d.ts +10 -11
  99. package/build/engine/system/system.js +5 -2
  100. package/build/engine/time.d.ts +54 -0
  101. package/build/engine/time.js +77 -0
  102. package/build/index.d.ts +2 -1
  103. package/package.json +1 -1
  104. package/build/contrib/systems/physics-system/subsystems/collision-detection/dispersion-calculator/index.d.ts +0 -11
  105. package/build/contrib/systems/physics-system/subsystems/collision-detection/dispersion-calculator/index.js +0 -41
  106. package/build/contrib/utils/one-way-validator.d.ts +0 -12
  107. package/build/engine/data-lib/sort/index.d.ts +0 -1
  108. package/build/engine/data-lib/sort/index.js +0 -1
  109. package/build/engine/data-lib/sort/insertion-sort.d.ts +0 -1
  110. package/build/engine/data-lib/sort/insertion-sort.js +0 -14
@@ -3,8 +3,16 @@ import { Vector2 } from '../../../engine/math-lib';
3
3
  /**
4
4
  * RigidBody component for defining rigid body physics.
5
5
  *
6
- * Defines the physics properties for an actor. It's used by the
7
- * physics system to apply forces and impulses to the actor.
6
+ * Defines the physics properties for an actor. Dynamic bodies react to forces,
7
+ * impulses, gravity, collisions, and rotation. Static and kinematic bodies can
8
+ * participate in collisions but are not moved by solver impulses.
9
+ *
10
+ * Physics is simulated in world space.
11
+ *
12
+ * Rigid bodies may be parented only to actors with static transforms. A moving
13
+ * parent (for example, a dynamic or kinematic rigid body) and the physics
14
+ * simulation would both control the child's transform, so the result is
15
+ * undefined.
8
16
  *
9
17
  * @example
10
18
  * ```typescript
@@ -29,24 +37,54 @@ import { Vector2 } from '../../../engine/math-lib';
29
37
  export class RigidBody extends Component {
30
38
  _mass;
31
39
  _inverseMass;
40
+ _inertia;
41
+ _inverseInertia;
42
+ _restitution;
43
+ _friction;
32
44
  /** @internal Pending one-step kinematic movement target */
33
45
  _movementTarget;
46
+ /** @internal Linear velocity from the start of the current physics step */
47
+ _prevLinearVelocity;
48
+ /** @internal Angular velocity from the start of the current physics step */
49
+ _prevAngularVelocity;
50
+ /** @internal Temporary solver velocity used for contact separation */
51
+ _biasLinearVelocity;
52
+ /** @internal Temporary solver angular velocity used for contact separation */
53
+ _biasAngularVelocity;
54
+ /** @internal Time this body has stayed below automatic sleep thresholds */
55
+ _sleepTime;
34
56
  /** Body type that defines how the rigid body participates in simulation */
35
57
  type;
36
- /** Gravity scale of the rigid body */
58
+ /** Gravity multiplier. `0` ignores gravity, `1` uses normal world gravity. */
37
59
  gravityScale;
38
- /** Linear damping value used to slow down movement over time */
60
+ /** Linear damping used to slow down movement over time */
39
61
  linearDamping;
40
- /** Current linear velocity of the rigid body */
62
+ /** Angular damping used to slow down rotation over time */
63
+ angularDamping;
64
+ /** Current linear velocity in world units per second */
41
65
  linearVelocity;
66
+ /** Current angular velocity of the rigid body in radians per second */
67
+ angularVelocity;
68
+ /** Whether dynamic rotation is locked. Locked bodies do not spin from torque or contacts. */
69
+ lockRotation;
42
70
  /** Whether rigid body simulation is disabled */
43
71
  disabled;
44
72
  /** Whether rigid body is sleeping */
45
73
  sleeping;
46
- /** Force applied to the rigid body */
47
- force;
48
- /** Impulse applied to the rigid body */
49
- impulse;
74
+ /** Whether this body may be put to sleep automatically when settled */
75
+ autoSleep;
76
+ /** @internal Force applied at the rigid body center */
77
+ _centralForce;
78
+ /** @internal Impulse applied at the rigid body center */
79
+ _centralImpulse;
80
+ /** @internal Forces applied at world-space positions */
81
+ _pointForces;
82
+ /** @internal Impulses applied at world-space positions */
83
+ _pointImpulses;
84
+ /** @internal Torque applied to the rigid body */
85
+ _torque;
86
+ /** @internal Angular impulse applied to the rigid body */
87
+ _angularImpulse;
50
88
  /** Whether contacts should only be resolved from one side */
51
89
  oneWay;
52
90
  /** Local-space normal that points toward the blocking side */
@@ -59,16 +97,36 @@ export class RigidBody extends Component {
59
97
  super();
60
98
  this._mass = 0;
61
99
  this._inverseMass = 0;
100
+ this._inertia = 0;
101
+ this._inverseInertia = 0;
102
+ this._restitution = 0;
103
+ this._friction = 0;
62
104
  this._movementTarget = null;
105
+ this._prevLinearVelocity = new Vector2(0, 0);
106
+ this._prevAngularVelocity = 0;
107
+ this._biasLinearVelocity = new Vector2(0, 0);
108
+ this._biasAngularVelocity = 0;
109
+ this._sleepTime = 0;
63
110
  this.type = config.type;
64
- this.mass = config.mass ?? 0;
65
- this.gravityScale = config.gravityScale ?? 0;
111
+ this.mass = config.mass ?? (this.type === 'dynamic' ? 1 : 0);
112
+ this.gravityScale =
113
+ config.gravityScale ?? (this.type === 'dynamic' ? 1 : 0);
66
114
  this.linearDamping = config.linearDamping ?? 0;
67
- this.linearVelocity = new Vector2(0, 0);
115
+ this.angularDamping = config.angularDamping ?? 0;
116
+ this.restitution = config.restitution ?? 0;
117
+ this.friction = config.friction ?? 0.6;
118
+ this.lockRotation = config.lockRotation ?? false;
119
+ this.autoSleep = config.autoSleep ?? true;
68
120
  this.disabled = config.disabled;
121
+ this.linearVelocity = new Vector2(0, 0);
122
+ this.angularVelocity = 0;
69
123
  this.sleeping = false;
70
- this.force = new Vector2(0, 0);
71
- this.impulse = new Vector2(0, 0);
124
+ this._centralForce = new Vector2(0, 0);
125
+ this._centralImpulse = new Vector2(0, 0);
126
+ this._pointForces = [];
127
+ this._pointImpulses = [];
128
+ this._torque = 0;
129
+ this._angularImpulse = 0;
72
130
  this.oneWay = config.oneWay ?? false;
73
131
  if (this.oneWay) {
74
132
  this.oneWayNormal = new Vector2(config.oneWayNormalX ?? 0, config.oneWayNormalY ?? 0).normalize();
@@ -83,6 +141,7 @@ export class RigidBody extends Component {
83
141
  /**
84
142
  * Sets the mass used by dynamic bodies.
85
143
  *
144
+ * Mass is an authored, kilogram-like scalar.
86
145
  * Non-positive values make the body immovable by forces and impulses.
87
146
  */
88
147
  set mass(value) {
@@ -95,50 +154,160 @@ export class RigidBody extends Component {
95
154
  * Bodies with zero or negative mass return `0`.
96
155
  */
97
156
  get inverseMass() {
157
+ if (this.type === 'static' || this.type === 'kinematic' || this.sleeping) {
158
+ return 0;
159
+ }
98
160
  return this._inverseMass;
99
161
  }
162
+ get inertia() {
163
+ return this._inertia;
164
+ }
165
+ /**
166
+ * Moment of inertia used by dynamic bodies.
167
+ *
168
+ * Inertia is the rotational equivalent of mass: higher values make the body
169
+ * harder to spin. It is automatically computed from the collider shape, mass,
170
+ * and collider offset every physics step, so user code usually should only
171
+ * read this value.
172
+ */
173
+ set inertia(value) {
174
+ this._inertia = value;
175
+ this._inverseInertia = value > 0 ? 1 / value : 0;
176
+ }
177
+ /**
178
+ * Returns the inverse moment of inertia.
179
+ *
180
+ * This is the solver-friendly form of inertia. Static bodies, kinematic
181
+ * bodies, locked rotation, or non-positive inertia return `0`.
182
+ */
183
+ get inverseInertia() {
184
+ if (this.type === 'static' ||
185
+ this.type === 'kinematic' ||
186
+ this.sleeping ||
187
+ this.lockRotation) {
188
+ return 0;
189
+ }
190
+ return this._inverseInertia;
191
+ }
192
+ /** Bounciness used by contact resolution. `0` does not bounce, `1` keeps full bounce speed. */
193
+ get restitution() {
194
+ return this._restitution;
195
+ }
196
+ set restitution(value) {
197
+ this._restitution = Math.max(0, Math.min(value, 1));
198
+ }
199
+ /** Surface friction used by contact resolution. Higher values reduce sliding more strongly. */
200
+ get friction() {
201
+ return this._friction;
202
+ }
203
+ set friction(value) {
204
+ this._friction = Math.max(0, value);
205
+ }
100
206
  /**
101
207
  * Adds a continuous force to a dynamic body for the next physics step.
102
208
  *
103
- * Affects only active dynamic bodies.
209
+ * Affects only active dynamic bodies. When `position` is provided, it is a
210
+ * world-space point where the force is applied; off-center forces can also
211
+ * rotate the body.
104
212
  */
105
- applyForce(force) {
213
+ applyForce(force, position) {
106
214
  if (this.disabled || this.type !== 'dynamic') {
107
215
  return;
108
216
  }
109
217
  this.wakeUp();
110
- this.force.add(force);
218
+ if (!position) {
219
+ this._centralForce.add(force);
220
+ return;
221
+ }
222
+ this._pointForces.push({
223
+ force: force.clone(),
224
+ position: { x: position.x, y: position.y },
225
+ });
111
226
  }
112
227
  /**
113
228
  * Adds an instantaneous impulse to a dynamic body for the next physics step.
114
229
  *
115
- * Affects only active dynamic bodies.
230
+ * Affects only active dynamic bodies. An impulse is a one-step velocity kick.
231
+ * When `position` is provided, it is a world-space point where the impulse is
232
+ * applied; off-center impulses can also rotate the body.
116
233
  */
117
- applyImpulse(impulse) {
234
+ applyImpulse(impulse, position) {
118
235
  if (this.disabled || this.type !== 'dynamic') {
119
236
  return;
120
237
  }
121
238
  this.wakeUp();
122
- this.impulse.add(impulse);
239
+ if (!position) {
240
+ this._centralImpulse.add(impulse);
241
+ return;
242
+ }
243
+ this._pointImpulses.push({
244
+ impulse: impulse.clone(),
245
+ position: { x: position.x, y: position.y },
246
+ });
247
+ }
248
+ /**
249
+ * Adds a continuous torque to a dynamic body for the next physics step.
250
+ *
251
+ * Torque is rotational force: it changes angular velocity over time. Affects
252
+ * only active dynamic bodies with unlocked rotation.
253
+ */
254
+ applyTorque(torque) {
255
+ if (this.disabled || this.type !== 'dynamic' || this.lockRotation) {
256
+ return;
257
+ }
258
+ this.wakeUp();
259
+ this._torque += torque;
260
+ }
261
+ /**
262
+ * Adds an instantaneous angular impulse to a dynamic body for the next physics step.
263
+ *
264
+ * Angular impulse is an immediate rotational kick. Affects only active dynamic
265
+ * bodies with unlocked rotation.
266
+ */
267
+ applyAngularImpulse(impulse) {
268
+ if (this.disabled || this.type !== 'dynamic' || this.lockRotation) {
269
+ return;
270
+ }
271
+ this.wakeUp();
272
+ this._angularImpulse += impulse;
123
273
  }
124
274
  /**
125
275
  * Marks the rigid body as awake so it can be simulated.
126
276
  */
127
277
  wakeUp() {
278
+ if (this.disabled || this.type !== 'dynamic') {
279
+ return;
280
+ }
128
281
  this.sleeping = false;
282
+ this._sleepTime = 0;
129
283
  }
130
284
  /**
131
285
  * Marks the rigid body as sleeping so dynamic integration can skip it.
132
286
  */
133
287
  sleep() {
288
+ if (this.disabled || this.type !== 'dynamic') {
289
+ return;
290
+ }
134
291
  this.sleeping = true;
292
+ this._sleepTime = 0;
293
+ this._prevLinearVelocity.multiplyNumber(0);
294
+ this._prevAngularVelocity = 0;
295
+ this.linearVelocity.multiplyNumber(0);
296
+ this.angularVelocity = 0;
297
+ this._biasLinearVelocity.multiplyNumber(0);
298
+ this._biasAngularVelocity = 0;
299
+ this.clearForces();
135
300
  }
136
301
  /**
137
302
  * Clears all accumulated force and impulse values.
138
303
  */
139
304
  clearForces() {
140
- this.force.multiplyNumber(0);
141
- this.impulse.multiplyNumber(0);
305
+ this._centralForce.multiplyNumber(0);
306
+ this._centralImpulse.multiplyNumber(0);
307
+ this._pointForces.length = 0;
308
+ this._pointImpulses.length = 0;
309
+ this._torque = 0;
310
+ this._angularImpulse = 0;
142
311
  }
143
312
  /**
144
313
  * Moves a kinematic body to a target position on the next physics step.
@@ -1,5 +1,5 @@
1
1
  import { SceneSystem } from '../../../engine/system';
2
- import type { UpdateOptions, SceneSystemOptions } from '../../../engine/system';
2
+ import type { SceneSystemOptions } from '../../../engine/system';
3
3
  /**
4
4
  * Animator system that manages the animation of actors with {@link Animatable} components
5
5
  * using the state machine approach
@@ -10,6 +10,7 @@ import type { UpdateOptions, SceneSystemOptions } from '../../../engine/system';
10
10
  */
11
11
  export declare class Animator extends SceneSystem {
12
12
  private actorQuery;
13
+ private time;
13
14
  private substatePickers;
14
15
  private actorConditions;
15
16
  constructor(options: SceneSystemOptions);
@@ -18,5 +19,5 @@ export declare class Animator extends SceneSystem {
18
19
  private setUpConditionControllers;
19
20
  private updateFrame;
20
21
  private pickSubstate;
21
- update(options: UpdateOptions): void;
22
+ update(): void;
22
23
  }
@@ -5,7 +5,8 @@ import { RemoveActor } from '../../../engine/events';
5
5
  import { conditionControllers } from './condition-controllers';
6
6
  import { substatePickers } from './substate-pickers';
7
7
  import { setValue } from './utils';
8
- const FRAME_RATE = 100;
8
+ // Seconds per animation frame at speed 1.
9
+ const FRAME_RATE = 0.1;
9
10
  /**
10
11
  * Animator system that manages the animation of actors with {@link Animatable} components
11
12
  * using the state machine approach
@@ -16,10 +17,12 @@ const FRAME_RATE = 100;
16
17
  */
17
18
  export class Animator extends SceneSystem {
18
19
  actorQuery;
20
+ time;
19
21
  substatePickers;
20
22
  actorConditions;
21
23
  constructor(options) {
22
24
  super();
25
+ this.time = options.time;
23
26
  this.actorQuery = new ActorQuery({
24
27
  scene: options.scene,
25
28
  filter: [Animatable],
@@ -64,8 +67,8 @@ export class Animator extends SceneSystem {
64
67
  const substatePicker = this.substatePickers[state.pickMode];
65
68
  return substatePicker.getSubstate(actor, state.substates, state.pickProps);
66
69
  }
67
- update(options) {
68
- const { deltaTime } = options;
70
+ update() {
71
+ const { deltaTime } = this.time;
69
72
  this.actorQuery.getActors().forEach((actor) => {
70
73
  const animatable = actor.getComponent(Animatable);
71
74
  if (animatable.currentState === void 0) {
@@ -1,5 +1,5 @@
1
1
  import { SceneSystem } from '../../../engine/system';
2
- import type { SceneSystemOptions, UpdateOptions } from '../../../engine/system';
2
+ import type { SceneSystemOptions } from '../../../engine/system';
3
3
  /**
4
4
  * Behavior system that manages custom behavior execution for actors
5
5
  * with {@link Behaviors} components
@@ -15,6 +15,7 @@ export declare class BehaviorSystem extends SceneSystem {
15
15
  private behaviors;
16
16
  private world;
17
17
  private scene;
18
+ private time;
18
19
  private activeBehaviors;
19
20
  constructor(options: SceneSystemOptions);
20
21
  onSceneEnter(): void;
@@ -22,5 +23,6 @@ export declare class BehaviorSystem extends SceneSystem {
22
23
  private handleActorAdd;
23
24
  private handleActorRemove;
24
25
  private setUpBehavior;
25
- update(options: UpdateOptions): void;
26
+ update(): void;
27
+ fixedUpdate(): void;
26
28
  }
@@ -17,12 +17,14 @@ export class BehaviorSystem extends SceneSystem {
17
17
  behaviors;
18
18
  world;
19
19
  scene;
20
+ time;
20
21
  activeBehaviors;
21
22
  constructor(options) {
22
23
  super();
23
- const { actorSpawner, world, scene, globalOptions, resources = [], } = options;
24
+ const { actorSpawner, world, scene, globalOptions, time, resources = [], } = options;
24
25
  this.world = world;
25
26
  this.scene = scene;
27
+ this.time = time;
26
28
  this.behaviorQuery = new ActorQuery({
27
29
  scene,
28
30
  filter: [Behaviors],
@@ -79,14 +81,20 @@ export class BehaviorSystem extends SceneSystem {
79
81
  world: this.world,
80
82
  scene: this.scene,
81
83
  globalOptions: this.globalOptions,
84
+ time: this.time,
82
85
  };
83
86
  const BehaviorClass = this.behaviors[config.name];
84
87
  return new BehaviorClass(options);
85
88
  });
86
89
  }
87
- update(options) {
90
+ update() {
88
91
  this.behaviorQuery.getActors().forEach((actor) => {
89
- this.activeBehaviors[actor.id].forEach((behavior) => behavior.update?.(options));
92
+ this.activeBehaviors[actor.id].forEach((behavior) => behavior.update?.());
93
+ });
94
+ }
95
+ fixedUpdate() {
96
+ this.behaviorQuery.getActors().forEach((actor) => {
97
+ this.activeBehaviors[actor.id].forEach((behavior) => behavior.fixedUpdate?.());
90
98
  });
91
99
  }
92
100
  }
@@ -1,6 +1,7 @@
1
1
  import type { Actor, ActorSpawner } from '../../../engine/actor';
2
2
  import type { World } from '../../../engine/world';
3
3
  import type { Scene } from '../../../engine/scene';
4
+ import type { Time } from '../../../engine/time';
4
5
  import type { Constructor } from '../../../types/utils';
5
6
  /**
6
7
  * Options for the behavior
@@ -16,13 +17,8 @@ export interface BehaviorOptions {
16
17
  actorSpawner: ActorSpawner;
17
18
  /** Global game options */
18
19
  globalOptions: Record<string, unknown>;
19
- }
20
- /**
21
- * Options for the update method
22
- */
23
- interface UpdateOptions {
24
- /** Time elapsed since the last update in milliseconds */
25
- deltaTime: number;
20
+ /** Shared timing state */
21
+ time: Time;
26
22
  }
27
23
  /**
28
24
  * Base class for all behaviors
@@ -35,8 +31,10 @@ export declare abstract class Behavior {
35
31
  static behaviorName: string;
36
32
  /** Destroy the behavior */
37
33
  destroy?(): void;
38
- /** Update the behavior */
39
- update?(options: UpdateOptions): void;
34
+ /** Update the behavior every frame with a variable timestep */
35
+ update?(): void;
36
+ /** Update the behavior with a fixed timestep, aligned with physics */
37
+ fixedUpdate?(): void;
40
38
  }
41
39
  /**
42
40
  * Constructor for all behaviors
@@ -44,4 +42,3 @@ export declare abstract class Behavior {
44
42
  export type BehaviorConstructor = Constructor<Behavior> & {
45
43
  behaviorName: string;
46
44
  };
47
- export {};
@@ -1,5 +1,5 @@
1
1
  import { SceneSystem } from '../../../engine/system';
2
- import type { SceneSystemOptions, UpdateOptions } from '../../../engine/system';
2
+ import type { SceneSystemOptions } from '../../../engine/system';
3
3
  /**
4
4
  * Kinematic character controller system with sweep/slide collision movement.
5
5
  *
@@ -13,9 +13,11 @@ import type { SceneSystemOptions, UpdateOptions } from '../../../engine/system';
13
13
  export declare class CharacterController extends SceneSystem {
14
14
  private actorQuery;
15
15
  private world;
16
+ private time;
16
17
  private oneWayValidator;
17
18
  constructor(options: SceneSystemOptions);
18
19
  onSceneDestroy(): void;
20
+ private handleRemoveActor;
19
21
  private isBlockingHit;
20
22
  private isWalkable;
21
23
  private isRecoverableOverlap;
@@ -28,5 +30,5 @@ export declare class CharacterController extends SceneSystem {
28
30
  private handleHit;
29
31
  private move;
30
32
  private updateGroundState;
31
- fixedUpdate(options: UpdateOptions): void;
33
+ fixedUpdate(): void;
32
34
  }
@@ -3,8 +3,9 @@ import { SceneSystem } from '../../../engine/system';
3
3
  import { Vector2, VectorOps } from '../../../engine/math-lib';
4
4
  import { Collider, CharacterBody, RigidBody, Transform, } from '../../components';
5
5
  import { PhysicsAPI } from '../physics-system';
6
- import { OneWayValidator } from '../../utils/one-way-validator';
7
6
  import { CharacterHit } from '../../events';
7
+ import { RemoveActor } from '../../../engine/events';
8
+ import { OneWayValidator } from './one-way-validator';
8
9
  import { clipAgainstNormal } from './utils';
9
10
  const DISTANCE_EPSILON = 0.000001;
10
11
  const SNAP_EPSILON = 0.000001;
@@ -21,19 +22,26 @@ const SNAP_EPSILON = 0.000001;
21
22
  export class CharacterController extends SceneSystem {
22
23
  actorQuery;
23
24
  world;
25
+ time;
24
26
  oneWayValidator;
25
27
  constructor(options) {
26
28
  super();
27
29
  this.world = options.world;
30
+ this.time = options.time;
28
31
  this.actorQuery = new ActorQuery({
29
32
  scene: options.scene,
30
33
  filter: [CharacterBody, Transform, Collider, RigidBody],
31
34
  });
35
+ this.actorQuery.addEventListener(RemoveActor, this.handleRemoveActor);
32
36
  this.oneWayValidator = new OneWayValidator();
33
37
  }
34
38
  onSceneDestroy() {
39
+ this.actorQuery.removeEventListener(RemoveActor, this.handleRemoveActor);
35
40
  this.actorQuery.destroy();
36
41
  }
42
+ handleRemoveActor = (event) => {
43
+ this.oneWayValidator.delete(event.actor);
44
+ };
37
45
  isBlockingHit(actor, hit) {
38
46
  const rigidBody = hit.actor.getComponent(RigidBody);
39
47
  if (!rigidBody || rigidBody.disabled) {
@@ -42,22 +50,18 @@ export class CharacterController extends SceneSystem {
42
50
  if (!rigidBody?.oneWay || !rigidBody.oneWayNormal) {
43
51
  return true;
44
52
  }
45
- return this.oneWayValidator.validate(hit.actor, actor, hit.normal);
53
+ return this.oneWayValidator.shouldBlock(hit.actor, actor, hit.normal);
46
54
  }
47
55
  isWalkable(actor, normal) {
48
56
  const character = actor.getComponent(CharacterBody);
49
57
  return (VectorOps.dotProduct(normal, character.upDirection) >=
50
58
  Math.cos(character.maxSlopeAngle));
51
59
  }
52
- isRecoverableOverlap(actor, hit) {
60
+ isRecoverableOverlap(hit) {
53
61
  const rigidBody = hit.actor.getComponent(RigidBody);
54
- if (!rigidBody || rigidBody.disabled || rigidBody.type === 'dynamic') {
55
- return false;
56
- }
57
- if (!rigidBody.oneWay || !rigidBody.oneWayNormal) {
58
- return true;
59
- }
60
- return this.oneWayValidator.validate(hit.actor, actor, hit.normal);
62
+ return (rigidBody !== undefined &&
63
+ !rigidBody.disabled &&
64
+ rigidBody.type !== 'dynamic');
61
65
  }
62
66
  resetGroundState(character) {
63
67
  if (character.onGround) {
@@ -79,6 +83,7 @@ export class CharacterController extends SceneSystem {
79
83
  const character = actor.getComponent(CharacterBody);
80
84
  const transform = actor.getComponent(Transform);
81
85
  const distance = displacement.magnitude;
86
+ this.oneWayValidator.touch(actor);
82
87
  return physicsApi.castActor({
83
88
  actor,
84
89
  offset: {
@@ -87,7 +92,7 @@ export class CharacterController extends SceneSystem {
87
92
  },
88
93
  direction: displacement,
89
94
  maxDistance: distance + character.skinWidth,
90
- hitFilter,
95
+ hitFilter: (hit) => this.isBlockingHit(actor, hit) && hitFilter(hit),
91
96
  });
92
97
  }
93
98
  castMotion(actor, position, displacement) {
@@ -96,11 +101,11 @@ export class CharacterController extends SceneSystem {
96
101
  VectorOps.dotProduct(displacement, hit.normal) > DISTANCE_EPSILON) {
97
102
  return false;
98
103
  }
99
- return this.isBlockingHit(actor, hit);
104
+ return true;
100
105
  });
101
106
  }
102
107
  castGround(actor, position, displacement) {
103
- return this.cast(actor, position, displacement, (hit) => this.isBlockingHit(actor, hit) && this.isWalkable(actor, hit.normal));
108
+ return this.cast(actor, position, displacement, (hit) => this.isWalkable(actor, hit.normal));
104
109
  }
105
110
  recoverOverlaps(actor, position) {
106
111
  const character = actor.getComponent(CharacterBody);
@@ -119,7 +124,7 @@ export class CharacterController extends SceneSystem {
119
124
  });
120
125
  let recovered = false;
121
126
  for (const hit of hits) {
122
- if (!this.isRecoverableOverlap(actor, hit)) {
127
+ if (!this.isRecoverableOverlap(hit)) {
123
128
  continue;
124
129
  }
125
130
  const correction = hit.penetration + character.skinWidth;
@@ -220,11 +225,11 @@ export class CharacterController extends SceneSystem {
220
225
  target.x += snapDirection.x;
221
226
  target.y += snapDirection.y;
222
227
  }
223
- fixedUpdate(options) {
228
+ fixedUpdate() {
224
229
  if (!this.world.systemApi.has(PhysicsAPI)) {
225
230
  return;
226
231
  }
227
- const deltaTimeInSeconds = options.deltaTime / 1000;
232
+ const { fixedDeltaTime } = this.time;
228
233
  this.oneWayValidator.update();
229
234
  this.actorQuery.getActors().forEach((actor) => {
230
235
  const transform = actor.getComponent(Transform);
@@ -243,7 +248,7 @@ export class CharacterController extends SceneSystem {
243
248
  this.recoverOverlaps(actor, position);
244
249
  const displacement = character.velocity
245
250
  .clone()
246
- .multiplyNumber(deltaTimeInSeconds)
251
+ .multiplyNumber(fixedDeltaTime)
247
252
  .add(character._displacement);
248
253
  const movingUp = VectorOps.dotProduct(displacement, character.upDirection) >
249
254
  SNAP_EPSILON;
@@ -0,0 +1,15 @@
1
+ import type { Actor } from '../../../engine/actor';
2
+ import { type Point } from '../../../engine/math-lib';
3
+ export declare class OneWayValidator {
4
+ private ignoredOneWayContacts;
5
+ private touchedActors;
6
+ private oneWayContactUpdateIndex;
7
+ constructor();
8
+ private clearOneWayContacts;
9
+ private trackOneWayContact;
10
+ touch(actor: Actor): void;
11
+ shouldBlock(oneWayActor: Actor, otherActor: Actor, normal: Point): boolean;
12
+ delete(actor: Actor): void;
13
+ update(): void;
14
+ lateUpdate(): void;
15
+ }