@caperjs/plugin-crunch 0.1.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.
package/src/Solid.ts ADDED
@@ -0,0 +1,380 @@
1
+ import { Actor } from './Actor';
2
+ import { Entity } from './Entity';
3
+ import { Sensor } from './Sensor';
4
+ import { EntityData, PhysicsEntityConfig, PhysicsEntityType } from './types';
5
+
6
+ /**
7
+ * Static or moving solid object that other entities can collide with.
8
+ * Solids are typically used for platforms, walls, and other obstacles.
9
+ *
10
+ * Features:
11
+ * - Static collision boundaries
12
+ * - Support for moving platforms
13
+ * - Carries actors and sensors that are riding it
14
+ * - Pushes overlapping entities out of the way
15
+ *
16
+ * @typeParam T - Application type, defaults to base Application
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * // Create a static platform
21
+ * const platform = physics.createSolid({
22
+ * type: 'Platform',
23
+ * position: [0, 500],
24
+ * size: [800, 32],
25
+ * view: platformSprite
26
+ * });
27
+ *
28
+ * // Create a moving platform
29
+ * const movingPlatform = physics.createSolid({
30
+ * type: 'Platform',
31
+ * position: [100, 300],
32
+ * size: [200, 32],
33
+ * view: platformSprite
34
+ * });
35
+ *
36
+ * // Move the platform back and forth
37
+ * gsap.to(movingPlatform, {
38
+ * x: 500,
39
+ * duration: 2,
40
+ * yoyo: true,
41
+ * repeat: -1
42
+ * });
43
+ * ```
44
+ */
45
+ export class Solid<D extends EntityData = EntityData> extends Entity<D> {
46
+ public readonly entityType: PhysicsEntityType = 'Solid';
47
+ /** Whether this solid should be removed when culled (typically false) */
48
+ public shouldRemoveOnCull = false;
49
+
50
+ /** Whether this solid can be collided with */
51
+ private _canCollide: boolean = true;
52
+
53
+ get canCollide(): boolean {
54
+ return this._canCollide;
55
+ }
56
+
57
+ /** Whether this solid has collisions enabled */
58
+ public collideable: boolean = true;
59
+
60
+ /** Whether this solid is currently moving */
61
+ public moving: boolean = false;
62
+
63
+ private _nextX: number;
64
+ private _nextY: number;
65
+
66
+ public setPosition(x: number, y: number): void {
67
+ this.x = x;
68
+ this.y = y;
69
+ }
70
+
71
+ /**
72
+ * Sets the solid's X position.
73
+ * For moving solids, this queues the movement to be applied on next update.
74
+ */
75
+ public set x(value: number) {
76
+ this._nextX = Math.round(value);
77
+ this.moving = true;
78
+ }
79
+
80
+ public get x(): number {
81
+ return this._x;
82
+ }
83
+
84
+ /**
85
+ * Sets the solid's Y position.
86
+ * For moving solids, this queues the movement to be applied on next update.
87
+ */
88
+ public set y(value: number) {
89
+ this._nextY = Math.round(value);
90
+ this.moving = true;
91
+ }
92
+
93
+ public get y(): number {
94
+ return this._y;
95
+ }
96
+
97
+ /**
98
+ * Initializes or reinitializes the solid with new configuration.
99
+ *
100
+ * @param config - Configuration for the solid
101
+ */
102
+ public init(config: PhysicsEntityConfig<D>): void {
103
+ super.init(config);
104
+ if (config) {
105
+ this._nextX = this._x;
106
+ this._nextY = this._y;
107
+ }
108
+ }
109
+
110
+ /** Right edge X coordinate */
111
+ public get right(): number {
112
+ return this.x + this.width;
113
+ }
114
+
115
+ /** Left edge X coordinate */
116
+ public get left(): number {
117
+ return this.x;
118
+ }
119
+
120
+ /** Top edge Y coordinate */
121
+ public get top(): number {
122
+ return this.y;
123
+ }
124
+
125
+ /** Bottom edge Y coordinate */
126
+ public get bottom(): number {
127
+ return this.y + this.height;
128
+ }
129
+
130
+ /**
131
+ * Checks if this solid can collide with the given entity type.
132
+ * This method is kept for backward compatibility.
133
+ *
134
+ * @returns Always returns true as we're now using only collision layers/masks
135
+ */
136
+ canCollideWith(): boolean {
137
+ // First check if collisions are enabled at all
138
+ if (!this.collideable) {
139
+ return false;
140
+ }
141
+
142
+ // We're now using only collision layers/masks, so this method is simplified
143
+ return true;
144
+ }
145
+
146
+ /**
147
+ * Moves the solid by the specified amount, carrying any riding entities.
148
+ * Also pushes any overlapping entities out of the way.
149
+ *
150
+ * @param x - X distance to move
151
+ * @param y - Y distance to move
152
+ * @param actors - Set of actors to check for riding/pushing
153
+ * @param sensors - Set of sensors to check for riding/pushing
154
+ */
155
+ public move(
156
+ x: number,
157
+ y: number,
158
+ actors: Set<Actor> = this.system.actors,
159
+ sensors: Set<Sensor> = this.system.sensors,
160
+ force: boolean = false,
161
+ ): void {
162
+ if (!this.active) {
163
+ return;
164
+ }
165
+
166
+ // Calculate total movement including remainder
167
+ const totalX = x + (this._nextX - this._x);
168
+ const totalY = y + (this._nextY - this._y);
169
+
170
+ this._xRemainder += totalX;
171
+ this._yRemainder += totalY;
172
+
173
+ const moveX = Math.round(this._xRemainder);
174
+ const moveY = Math.round(this._yRemainder);
175
+
176
+ if (moveX !== 0 || moveY !== 0 || force) {
177
+ if (this.collideable) {
178
+ // Get all riding actors and sensors before movement
179
+ const ridingActors = new Set<Actor>();
180
+ const ridingSensors = new Set<Sensor>();
181
+
182
+ for (const actor of actors) {
183
+ // Check if actor can collide with this solid based on collision layers/masks
184
+ if (
185
+ (actor.collisionLayer & this.collisionMask) !== 0 &&
186
+ (this.collisionLayer & actor.collisionMask) !== 0 &&
187
+ actor.isRiding(this)
188
+ ) {
189
+ ridingActors.add(actor);
190
+ }
191
+ }
192
+
193
+ for (const sensor of sensors) {
194
+ // Check if sensor can collide with this solid based on collision layers/masks
195
+ if (
196
+ (sensor.collisionLayer & this.collisionMask) !== 0 &&
197
+ (this.collisionLayer & sensor.collisionMask) !== 0 &&
198
+ sensor.isRiding(this)
199
+ ) {
200
+ ridingSensors.add(sensor);
201
+ }
202
+ }
203
+
204
+ // Make this solid non-collidable so actors don't get stuck
205
+ this._canCollide = false;
206
+
207
+ if (moveX !== 0) {
208
+ this._xRemainder -= moveX;
209
+ this._x += moveX;
210
+
211
+ if (moveX > 0) {
212
+ // Moving right
213
+ for (const actor of actors) {
214
+ // Check if actor can collide with this solid based on collision layers/masks
215
+ if (
216
+ this.overlaps(actor) &&
217
+ (actor.collisionLayer & this.collisionMask) !== 0 &&
218
+ (this.collisionLayer & actor.collisionMask) !== 0
219
+ ) {
220
+ // Push right
221
+ actor.moveX(this.right - actor.x, actor.squish, this);
222
+ } else if (ridingActors.has(actor)) {
223
+ // Carry right
224
+ actor.moveX(moveX);
225
+ }
226
+ }
227
+ for (const sensor of sensors) {
228
+ // Check if sensor can collide with this solid based on collision layers/masks
229
+ if (
230
+ this.overlaps(sensor) &&
231
+ (sensor.collisionLayer & this.collisionMask) !== 0 &&
232
+ (this.collisionLayer & sensor.collisionMask) !== 0
233
+ ) {
234
+ // Push right
235
+ sensor.moveX(this.right - sensor.x);
236
+ } else if (ridingSensors.has(sensor)) {
237
+ // Carry right
238
+ sensor.moveX(moveX);
239
+ }
240
+ }
241
+ } else {
242
+ // Moving left
243
+ for (const actor of actors) {
244
+ // Check if actor can collide with this solid based on collision layers/masks
245
+ if (
246
+ this.overlaps(actor) &&
247
+ (actor.collisionLayer & this.collisionMask) !== 0 &&
248
+ (this.collisionLayer & actor.collisionMask) !== 0
249
+ ) {
250
+ // Push left
251
+ actor.moveX(this.left - (actor.x + actor.width), actor.squish, this);
252
+ } else if (ridingActors.has(actor)) {
253
+ // Carry left
254
+ actor.moveX(moveX);
255
+ }
256
+ }
257
+ for (const sensor of sensors) {
258
+ // Check if sensor can collide with this solid based on collision layers/masks
259
+ if (
260
+ this.overlaps(sensor) &&
261
+ (sensor.collisionLayer & this.collisionMask) !== 0 &&
262
+ (this.collisionLayer & sensor.collisionMask) !== 0
263
+ ) {
264
+ // Push left
265
+ sensor.moveX(this.left - (sensor.x + sensor.width));
266
+ } else if (ridingSensors.has(sensor)) {
267
+ // Carry left
268
+ sensor.moveX(moveX);
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ if (moveY !== 0) {
275
+ this._yRemainder -= moveY;
276
+ this._y += moveY;
277
+
278
+ if (moveY > 0) {
279
+ // Moving down
280
+ for (const actor of actors) {
281
+ // Check if actor can collide with this solid based on collision layers/masks
282
+ if (
283
+ this.overlaps(actor) &&
284
+ (actor.collisionLayer & this.collisionMask) !== 0 &&
285
+ (this.collisionLayer & actor.collisionMask) !== 0
286
+ ) {
287
+ // Push down
288
+ actor.moveY(this.bottom - actor.y, actor.squish, this);
289
+ } else if (ridingActors.has(actor)) {
290
+ // Carry down
291
+ actor.moveY(moveY);
292
+ }
293
+ }
294
+ for (const sensor of sensors) {
295
+ // Check if sensor can collide with this solid based on collision layers/masks
296
+ if (
297
+ this.overlaps(sensor) &&
298
+ (sensor.collisionLayer & this.collisionMask) !== 0 &&
299
+ (this.collisionLayer & sensor.collisionMask) !== 0
300
+ ) {
301
+ // Push down
302
+ sensor.moveY(this.bottom - sensor.y);
303
+ } else if (ridingSensors.has(sensor)) {
304
+ // Carry down
305
+ sensor.moveY(moveY);
306
+ }
307
+ }
308
+ } else {
309
+ // Moving up
310
+ for (const actor of actors) {
311
+ // Check if actor can collide with this solid based on collision layers/masks
312
+ if (
313
+ this.overlaps(actor) &&
314
+ (actor.collisionLayer & this.collisionMask) !== 0 &&
315
+ (this.collisionLayer & actor.collisionMask) !== 0
316
+ ) {
317
+ // Push up
318
+ actor.moveY(this.top - (actor.y + actor.height), actor.squish, this);
319
+ } else if (ridingActors.has(actor)) {
320
+ // Carry up
321
+ actor.moveY(moveY);
322
+ }
323
+ }
324
+ for (const sensor of sensors) {
325
+ // Check if sensor can collide with this solid based on collision layers/masks
326
+ if (
327
+ this.overlaps(sensor) &&
328
+ (sensor.collisionLayer & this.collisionMask) !== 0 &&
329
+ (this.collisionLayer & sensor.collisionMask) !== 0
330
+ ) {
331
+ // Push up
332
+ sensor.moveY(this.top - (sensor.y + sensor.height));
333
+ } else if (ridingSensors.has(sensor)) {
334
+ // Carry up
335
+ sensor.moveY(moveY);
336
+ }
337
+ }
338
+ }
339
+ }
340
+
341
+ // Re-enable collisions
342
+ this._canCollide = true;
343
+ } else {
344
+ // When collisions are disabled, just move without affecting other entities
345
+ if (moveX !== 0) {
346
+ this._xRemainder -= moveX;
347
+ this._x += moveX;
348
+ }
349
+ if (moveY !== 0) {
350
+ this._yRemainder -= moveY;
351
+ this._y += moveY;
352
+ }
353
+ }
354
+
355
+ // Update next positions to match current
356
+ this._nextX = this._x;
357
+ this._nextY = this._y;
358
+
359
+ // Update the view position
360
+ this.updateView();
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Checks if this solid overlaps with the given entity.
366
+ *
367
+ * @param entity - Entity to check for overlap
368
+ * @returns True if overlapping
369
+ */
370
+ private overlaps(entity: Actor | Sensor): boolean {
371
+ // Check collision layers and masks
372
+ // A collision occurs when (A.layer & B.mask) !== 0 && (B.layer & A.mask) !== 0
373
+ if ((this.collisionLayer & entity.collisionMask) === 0 || (entity.collisionLayer & this.collisionMask) === 0) {
374
+ return false;
375
+ }
376
+
377
+ // Check for AABB overlap
378
+ return this.system.aabbOverlap(this, entity);
379
+ }
380
+ }