@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/LICENSE +22 -0
- package/README.md +205 -0
- package/lib/Actor.d.ts +227 -0
- package/lib/Actor.d.ts.map +1 -0
- package/lib/CrunchPhysicsPlugin.d.ts +508 -0
- package/lib/CrunchPhysicsPlugin.d.ts.map +1 -0
- package/lib/Entity.d.ts +377 -0
- package/lib/Entity.d.ts.map +1 -0
- package/lib/Group.d.ts +122 -0
- package/lib/Group.d.ts.map +1 -0
- package/lib/Sensor.d.ts +161 -0
- package/lib/Sensor.d.ts.map +1 -0
- package/lib/Solid.d.ts +109 -0
- package/lib/Solid.d.ts.map +1 -0
- package/lib/System.d.ts +265 -0
- package/lib/System.d.ts.map +1 -0
- package/lib/caper-plugin-crunch.mjs +1331 -0
- package/lib/caper-plugin-crunch.mjs.map +1 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/interfaces.d.ts +32 -0
- package/lib/interfaces.d.ts.map +1 -0
- package/lib/types.d.ts +246 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/utils.d.ts +63 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/version.d.ts +2 -0
- package/lib/version.d.ts.map +1 -0
- package/package.json +42 -0
- package/src/Actor.ts +726 -0
- package/src/CrunchPhysicsPlugin.ts +718 -0
- package/src/Entity.ts +762 -0
- package/src/Group.ts +181 -0
- package/src/Sensor.ts +427 -0
- package/src/Solid.ts +380 -0
- package/src/System.ts +1546 -0
- package/src/collision-layers.d.ts +87 -0
- package/src/index.ts +18 -0
- package/src/interfaces.ts +33 -0
- package/src/types.ts +336 -0
- package/src/utils.ts +72 -0
- package/src/version.ts +1 -0
package/src/Entity.ts
ADDED
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Application,
|
|
3
|
+
bindAllMethods,
|
|
4
|
+
defaultFactoryMethods,
|
|
5
|
+
IApplication,
|
|
6
|
+
PointLike,
|
|
7
|
+
randomUUID,
|
|
8
|
+
resolvePointLike,
|
|
9
|
+
SignalConnection,
|
|
10
|
+
SignalConnections,
|
|
11
|
+
} from '@caperjs/core';
|
|
12
|
+
import CrunchPhysicsPlugin from './CrunchPhysicsPlugin';
|
|
13
|
+
import { Group } from './Group';
|
|
14
|
+
import { System } from './System';
|
|
15
|
+
import {
|
|
16
|
+
CollisionLayer,
|
|
17
|
+
EntityData,
|
|
18
|
+
PhysicsEntityConfig,
|
|
19
|
+
PhysicsEntityType,
|
|
20
|
+
PhysicsEntityView,
|
|
21
|
+
Rectangle,
|
|
22
|
+
} from './types';
|
|
23
|
+
import { resolveEntityPosition, resolveEntitySize } from './utils';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base class for all physics entities in the Crunch physics system.
|
|
27
|
+
* Provides common functionality for position, size, view management, and lifecycle.
|
|
28
|
+
*
|
|
29
|
+
* Entity is the foundation for:
|
|
30
|
+
* - Actors (dynamic objects)
|
|
31
|
+
* - Solids (static objects)
|
|
32
|
+
* - Sensors (trigger zones)
|
|
33
|
+
*
|
|
34
|
+
* It handles:
|
|
35
|
+
* - Position and size management
|
|
36
|
+
* - View (sprite) management and updates
|
|
37
|
+
* - Group membership and relative positioning
|
|
38
|
+
* - Culling and lifecycle states
|
|
39
|
+
* - Signal connections for event handling
|
|
40
|
+
*
|
|
41
|
+
* @typeParam A - Application type, defaults to base Application
|
|
42
|
+
* @typeParam D - Entity data type, defaults to base EntityData
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* // Create a custom entity
|
|
47
|
+
* class CustomEntity extends Entity {
|
|
48
|
+
* constructor() {
|
|
49
|
+
* super({
|
|
50
|
+
* type: 'Custom',
|
|
51
|
+
* position: [100, 100],
|
|
52
|
+
* size: [32, 32],
|
|
53
|
+
* view: sprite
|
|
54
|
+
* });
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* // Override update for custom behavior
|
|
58
|
+
* update(dt: number) {
|
|
59
|
+
* super.update(dt);
|
|
60
|
+
* // Custom update logic
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export class Entity<D extends EntityData = EntityData> {
|
|
66
|
+
public readonly entityType: PhysicsEntityType;
|
|
67
|
+
protected _id: string;
|
|
68
|
+
/** Unique type identifier for this entity */
|
|
69
|
+
public type!: string;
|
|
70
|
+
|
|
71
|
+
/** Color to use when rendering debug visuals */
|
|
72
|
+
public debugColor: number;
|
|
73
|
+
|
|
74
|
+
/** Whether the entity should be removed when culled (out of bounds) */
|
|
75
|
+
public shouldRemoveOnCull: boolean;
|
|
76
|
+
|
|
77
|
+
/** Entity width in pixels */
|
|
78
|
+
public width: number;
|
|
79
|
+
|
|
80
|
+
/** Entity height in pixels */
|
|
81
|
+
public height: number;
|
|
82
|
+
|
|
83
|
+
/** Visual representation (sprite/graphics) of this entity */
|
|
84
|
+
public view!: PhysicsEntityView;
|
|
85
|
+
|
|
86
|
+
/** Whether this entity is active and should be updated */
|
|
87
|
+
|
|
88
|
+
/** Collision layer this entity belongs to (bitwise) */
|
|
89
|
+
public collisionLayer: number = CollisionLayer.NONE;
|
|
90
|
+
|
|
91
|
+
/** Collision mask defining which layers this entity collides with (bitwise) */
|
|
92
|
+
public collisionMask: number = CollisionLayer.NONE;
|
|
93
|
+
|
|
94
|
+
protected _data: Partial<D>;
|
|
95
|
+
protected _group: Group | null;
|
|
96
|
+
protected _groupOffset: { x: number; y: number };
|
|
97
|
+
protected _isCulled: boolean;
|
|
98
|
+
protected _isDestroyed: boolean;
|
|
99
|
+
protected _isInitialized: boolean;
|
|
100
|
+
protected _xRemainder: number;
|
|
101
|
+
protected _yRemainder: number;
|
|
102
|
+
protected _x: number;
|
|
103
|
+
protected _y: number;
|
|
104
|
+
protected signalConnections: SignalConnections;
|
|
105
|
+
protected _following: Entity | null;
|
|
106
|
+
protected _followOffset: { x: number; y: number };
|
|
107
|
+
protected _config: PhysicsEntityConfig<D> | undefined;
|
|
108
|
+
protected _active: boolean = true;
|
|
109
|
+
|
|
110
|
+
public updatedFollowPosition: boolean = false;
|
|
111
|
+
public updatedGroupPosition: boolean = false;
|
|
112
|
+
|
|
113
|
+
set active(value: boolean) {
|
|
114
|
+
this._active = value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get active(): boolean {
|
|
118
|
+
return this._active;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
set id(value: string) {
|
|
122
|
+
this._id = value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get id(): string {
|
|
126
|
+
return this._id || this.type;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
get make(): typeof defaultFactoryMethods {
|
|
130
|
+
return this.app.make;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Custom data associated with this entity
|
|
135
|
+
*/
|
|
136
|
+
set data(value: Partial<D>) {
|
|
137
|
+
this._data = value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get data(): Partial<D> {
|
|
141
|
+
return this._data;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setFollowing(entityToFollow: Entity | null, offset: PointLike = { x: 0, y: 0 }) {
|
|
145
|
+
this._followOffset = resolvePointLike(offset);
|
|
146
|
+
if (this._following) {
|
|
147
|
+
this.system.removeFollower(this);
|
|
148
|
+
}
|
|
149
|
+
this._following = entityToFollow;
|
|
150
|
+
if (entityToFollow) {
|
|
151
|
+
this.system.addFollower(entityToFollow, this);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get followOffset(): { x: number; y: number } {
|
|
156
|
+
return this._followOffset || { x: 0, y: 0 };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get following(): Entity | null {
|
|
160
|
+
return this._following;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
get followers(): Entity[] {
|
|
164
|
+
return this.system.getFollowersOf(this);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The group this entity belongs to, if any.
|
|
169
|
+
* Groups allow for collective movement and management of entities.
|
|
170
|
+
*/
|
|
171
|
+
get group(): Group | null {
|
|
172
|
+
return this._group;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
set groupOffset(value: { x: number; y: number }) {
|
|
176
|
+
this._groupOffset = resolvePointLike(value);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
get groupOffset(): { x: number; y: number } {
|
|
180
|
+
return this._groupOffset || { x: 0, y: 0 };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
setGroup(group: Group | null, offset: PointLike = { x: 0, y: 0 }) {
|
|
184
|
+
this._groupOffset = resolvePointLike(offset);
|
|
185
|
+
// if we're already in a group, remove ourselves first
|
|
186
|
+
if (this._group) {
|
|
187
|
+
this.system.removeFromGroup(this);
|
|
188
|
+
this.onRemovedFromGroup();
|
|
189
|
+
}
|
|
190
|
+
this._group = group;
|
|
191
|
+
if (group) {
|
|
192
|
+
this.system.addToGroup(group, this);
|
|
193
|
+
this.onAddedToGroup();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
set position(value: PointLike) {
|
|
198
|
+
const { x, y } = resolvePointLike(value);
|
|
199
|
+
this.setPosition(x, y);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
get position(): PointLike {
|
|
203
|
+
return { x: this.x, y: this.y };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Entity's X position in world space.
|
|
208
|
+
* If the entity belongs to a group, returns position relative to group.
|
|
209
|
+
*/
|
|
210
|
+
set x(value: number) {
|
|
211
|
+
this._x = value;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
get x(): number {
|
|
215
|
+
if (this._group) {
|
|
216
|
+
return Math.round(this._x + this._group.getChildOffset(this).x); // Return world position
|
|
217
|
+
}
|
|
218
|
+
return Math.round(this._x);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Entity's Y position in world space.
|
|
223
|
+
* If the entity belongs to a group, returns position relative to group.
|
|
224
|
+
*/
|
|
225
|
+
set y(value: number) {
|
|
226
|
+
this._y = value;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
get y(): number {
|
|
230
|
+
if (this._group) {
|
|
231
|
+
return Math.round(this._y + this._group.getChildOffset(this).y); // Return world position
|
|
232
|
+
}
|
|
233
|
+
return Math.round(this._y);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Whether this entity is currently culled (out of bounds) */
|
|
237
|
+
get isCulled(): boolean {
|
|
238
|
+
return this._isCulled;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Whether this entity has been destroyed */
|
|
242
|
+
get isDestroyed(): boolean {
|
|
243
|
+
return this._isDestroyed;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Reference to the main application instance */
|
|
247
|
+
get app(): IApplication {
|
|
248
|
+
return Application.getInstance();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Reference to the physics plugin */
|
|
252
|
+
get physics(): CrunchPhysicsPlugin {
|
|
253
|
+
return this.app.getPlugin('crunch') as CrunchPhysicsPlugin;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Creates a new Entity instance.
|
|
258
|
+
*
|
|
259
|
+
* @param config - Optional configuration for the entity
|
|
260
|
+
*/
|
|
261
|
+
constructor(config?: PhysicsEntityConfig<D>) {
|
|
262
|
+
bindAllMethods(this);
|
|
263
|
+
|
|
264
|
+
this._config = config;
|
|
265
|
+
|
|
266
|
+
this.signalConnections = new SignalConnections();
|
|
267
|
+
this.shouldRemoveOnCull = false;
|
|
268
|
+
this.width = 0;
|
|
269
|
+
this.height = 0;
|
|
270
|
+
|
|
271
|
+
this._data = {};
|
|
272
|
+
this._group = null;
|
|
273
|
+
this._isCulled = false;
|
|
274
|
+
this._isDestroyed = false;
|
|
275
|
+
this._isInitialized = false;
|
|
276
|
+
this._xRemainder = 0;
|
|
277
|
+
this._yRemainder = 0;
|
|
278
|
+
|
|
279
|
+
this._x = 0;
|
|
280
|
+
this._y = 0;
|
|
281
|
+
|
|
282
|
+
if (config) {
|
|
283
|
+
this.init(config);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.initialize();
|
|
287
|
+
this.addView();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Called after construction to perform additional initialization.
|
|
292
|
+
* Override this in subclasses to add custom initialization logic.
|
|
293
|
+
*/
|
|
294
|
+
protected initialize() {
|
|
295
|
+
// Override in subclass
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Called before update to prepare for the next frame.
|
|
300
|
+
* Override this in subclasses to add pre-update logic.
|
|
301
|
+
*/
|
|
302
|
+
public preUpdate(): void {
|
|
303
|
+
// Override in subclass
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Called every frame to update the entity's state.
|
|
308
|
+
* Override this in subclasses to add update logic.
|
|
309
|
+
*
|
|
310
|
+
* @param dt - Delta time in seconds since last update
|
|
311
|
+
*/
|
|
312
|
+
public update(dt: number): void {
|
|
313
|
+
// Override in subclass
|
|
314
|
+
void dt;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Called after update to finalize the frame.
|
|
319
|
+
* Override this in subclasses to add post-update logic.
|
|
320
|
+
*/
|
|
321
|
+
public postUpdate(): void {
|
|
322
|
+
// Override in subclass
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Excludes collision types for this entity
|
|
327
|
+
* @deprecated Use setCollisionMask instead
|
|
328
|
+
*/
|
|
329
|
+
excludeCollisionType() {
|
|
330
|
+
console.warn('excludeCollisionType is deprecated. Use setCollisionMask instead.');
|
|
331
|
+
// No-op as we're removing this functionality
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Includes collision types for this entity
|
|
336
|
+
* @deprecated Use setCollisionMask instead
|
|
337
|
+
*/
|
|
338
|
+
includeCollisionType() {
|
|
339
|
+
console.warn('includeCollisionType is deprecated. Use setCollisionMask instead.');
|
|
340
|
+
// No-op as we're removing this functionality
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Removes collision types for this entity
|
|
345
|
+
* @deprecated Use removeCollisionMask instead
|
|
346
|
+
*/
|
|
347
|
+
removeCollisionType() {
|
|
348
|
+
console.warn('removeCollisionType is deprecated. Use removeCollisionMask instead.');
|
|
349
|
+
// No-op as we're removing this functionality
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Adds collision types for this entity
|
|
354
|
+
* @deprecated Use addCollisionMask instead
|
|
355
|
+
*/
|
|
356
|
+
addCollisionType() {
|
|
357
|
+
console.warn('addCollisionType is deprecated. Use addCollisionMask instead.');
|
|
358
|
+
// No-op as we're removing this functionality
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Checks if this entity can collide with a specific type
|
|
363
|
+
*/
|
|
364
|
+
canCollideWith(): boolean {
|
|
365
|
+
// Always return true as we're now using only collision layers/masks
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Adds the entity's view to the physics container and updates its position.
|
|
371
|
+
*/
|
|
372
|
+
protected addView() {
|
|
373
|
+
if (this.view) {
|
|
374
|
+
this.view.visible = true;
|
|
375
|
+
this.view.label = this.id || this.type;
|
|
376
|
+
if (this.system.container) {
|
|
377
|
+
this.system.container.addChild(this.view);
|
|
378
|
+
this.updateView();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Initializes or reinitializes the entity with new configuration.
|
|
385
|
+
* Used by object pools when recycling entities.
|
|
386
|
+
*
|
|
387
|
+
* @param config - New configuration to apply
|
|
388
|
+
*/
|
|
389
|
+
public init(config: PhysicsEntityConfig<D>): void {
|
|
390
|
+
if (!config) return;
|
|
391
|
+
this._config = config as PhysicsEntityConfig<D>;
|
|
392
|
+
|
|
393
|
+
if (config.id) {
|
|
394
|
+
this._id = config.id;
|
|
395
|
+
} else {
|
|
396
|
+
this._id = randomUUID();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (config.type) {
|
|
400
|
+
this.type = config.type;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (config.data) {
|
|
404
|
+
this._data = config.data as Partial<D>;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const position = resolveEntityPosition(config);
|
|
408
|
+
this._x = position.x;
|
|
409
|
+
this._y = position.y;
|
|
410
|
+
|
|
411
|
+
const size = resolveEntitySize(config);
|
|
412
|
+
this.width = size.width;
|
|
413
|
+
this.height = size.height;
|
|
414
|
+
|
|
415
|
+
// Initialize collision layers
|
|
416
|
+
if (config.collisionLayer !== undefined) {
|
|
417
|
+
this.setCollisionLayer(config.collisionLayer);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (config.collisionMask !== undefined) {
|
|
421
|
+
this.setCollisionMask(config.collisionMask);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Reset physics properties
|
|
425
|
+
this._xRemainder = 0;
|
|
426
|
+
this._yRemainder = 0;
|
|
427
|
+
|
|
428
|
+
if (config.view) {
|
|
429
|
+
this.setView(config.view);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (config.group) {
|
|
433
|
+
this.setGroup(config.group ?? null, config.groupOffset ? resolvePointLike(config.groupOffset) : { x: 0, y: 0 });
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (config.follows) {
|
|
437
|
+
this.setFollowing(
|
|
438
|
+
config.follows ?? null,
|
|
439
|
+
config.followOffset ? resolvePointLike(config.followOffset) : { x: 0, y: 0 },
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
// Show and update view if it exists
|
|
443
|
+
this.addView();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Resets the entity to its initial state for reuse in object pools.
|
|
448
|
+
* Override this to handle custom reset logic.
|
|
449
|
+
*/
|
|
450
|
+
public reset(): void {
|
|
451
|
+
// Reset culling state
|
|
452
|
+
this._isCulled = false;
|
|
453
|
+
this._isDestroyed = false;
|
|
454
|
+
|
|
455
|
+
// Reset remainders
|
|
456
|
+
this._xRemainder = 0;
|
|
457
|
+
this._yRemainder = 0;
|
|
458
|
+
|
|
459
|
+
this._followOffset = { x: 0, y: 0 };
|
|
460
|
+
this._following = null;
|
|
461
|
+
|
|
462
|
+
this._groupOffset = { x: 0, y: 0 };
|
|
463
|
+
this._group = null;
|
|
464
|
+
|
|
465
|
+
this._data = {};
|
|
466
|
+
|
|
467
|
+
this._x = -Number.MAX_SAFE_INTEGER;
|
|
468
|
+
this._y = -Number.MAX_SAFE_INTEGER;
|
|
469
|
+
|
|
470
|
+
if (this.view) {
|
|
471
|
+
this.view.visible = false;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
this.system.removeEntity(this);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Reference to the physics system */
|
|
478
|
+
get system(): System {
|
|
479
|
+
return this.physics.system;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Called when the entity is added to a group.
|
|
484
|
+
* Override this to handle custom group addition logic.
|
|
485
|
+
*/
|
|
486
|
+
public onAddedToGroup(): void {
|
|
487
|
+
// Override in subclass
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Called when the entity is removed from a group.
|
|
492
|
+
* Override this to handle custom group removal logic.
|
|
493
|
+
*/
|
|
494
|
+
public onRemovedFromGroup(): void {
|
|
495
|
+
// Override in subclass
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Updates the entity's position and view.
|
|
500
|
+
*/
|
|
501
|
+
public updatePosition(): void {
|
|
502
|
+
this.x = this._x;
|
|
503
|
+
this.y = this._y;
|
|
504
|
+
this.updateView();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Called when the entity is culled (goes out of bounds).
|
|
509
|
+
* Override this to handle culling differently.
|
|
510
|
+
*/
|
|
511
|
+
public onCull(): void {
|
|
512
|
+
this._isCulled = true;
|
|
513
|
+
// Default behavior: hide the view
|
|
514
|
+
if (this.view) {
|
|
515
|
+
this.view.visible = false;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Called when the entity is brought back after being culled.
|
|
521
|
+
* Override this to handle unculling differently.
|
|
522
|
+
*/
|
|
523
|
+
public onUncull(): void {
|
|
524
|
+
this._isCulled = false;
|
|
525
|
+
// Default behavior: show the view
|
|
526
|
+
if (this.view) {
|
|
527
|
+
this.view.visible = true;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Prepares the entity for removal/recycling.
|
|
533
|
+
* Override this to handle custom cleanup.
|
|
534
|
+
*/
|
|
535
|
+
public destroy(): void {
|
|
536
|
+
if (this._isDestroyed) return;
|
|
537
|
+
|
|
538
|
+
this._isDestroyed = true;
|
|
539
|
+
this._isCulled = false;
|
|
540
|
+
|
|
541
|
+
this.signalConnections.disconnectAll();
|
|
542
|
+
|
|
543
|
+
// Don't destroy the view - it will be reused
|
|
544
|
+
if (this.view) {
|
|
545
|
+
this.view.visible = false;
|
|
546
|
+
this.view.removeFromParent();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
this.system.removeFollower(this);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Called when the entity is removed from the physics system.
|
|
554
|
+
* Override this to handle custom removal logic.
|
|
555
|
+
*/
|
|
556
|
+
public onRemoved(): void {
|
|
557
|
+
if (!this._isDestroyed) {
|
|
558
|
+
this.destroy();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Sets a new view for the entity and updates its position.
|
|
564
|
+
*
|
|
565
|
+
* @param view - The new view to use
|
|
566
|
+
*/
|
|
567
|
+
public setView(view: PhysicsEntityView): void {
|
|
568
|
+
this.view = view;
|
|
569
|
+
this.updateView();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Updates the view's position to match the entity's position.
|
|
574
|
+
*/
|
|
575
|
+
public updateView(): void {
|
|
576
|
+
if (this.view && this.view.visible && this.view.position) {
|
|
577
|
+
this.view.position.set(this.x, this.y);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Gets the entity's bounding rectangle.
|
|
583
|
+
*
|
|
584
|
+
* @returns Rectangle representing the entity's bounds
|
|
585
|
+
*/
|
|
586
|
+
public getBounds(): Rectangle {
|
|
587
|
+
return {
|
|
588
|
+
x: this.x,
|
|
589
|
+
y: this.y,
|
|
590
|
+
width: this.width,
|
|
591
|
+
height: this.height,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Sets the entity's position, resetting any movement remainders.
|
|
597
|
+
*
|
|
598
|
+
* @param x - New X position
|
|
599
|
+
* @param y - New Y position
|
|
600
|
+
*/
|
|
601
|
+
public setPosition(x: number, y: number): void {
|
|
602
|
+
this._x = x;
|
|
603
|
+
this._y = y;
|
|
604
|
+
this._xRemainder = 0;
|
|
605
|
+
this._yRemainder = 0;
|
|
606
|
+
this.updateView();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Alias for setPosition.
|
|
611
|
+
*
|
|
612
|
+
* @param x - New X position
|
|
613
|
+
* @param y - New Y position
|
|
614
|
+
*/
|
|
615
|
+
public moveTo(x: number, y: number): void {
|
|
616
|
+
this.setPosition(x, y);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Adds signal connections to the entity.
|
|
621
|
+
*
|
|
622
|
+
* @param args - Signal connections to add
|
|
623
|
+
*/
|
|
624
|
+
public addSignalConnection(...args: SignalConnection[]) {
|
|
625
|
+
for (const connection of args) {
|
|
626
|
+
this.signalConnections.add(connection);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Alias for addSignalConnection.
|
|
632
|
+
*
|
|
633
|
+
* @param args - Signal connections to add
|
|
634
|
+
*/
|
|
635
|
+
public connectSignal(...args: SignalConnection[]) {
|
|
636
|
+
for (const connection of args) {
|
|
637
|
+
this.signalConnections.add(connection);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Alias for addSignalConnection, specifically for action signals.
|
|
643
|
+
*
|
|
644
|
+
* @param args - Action signal connections to add
|
|
645
|
+
*/
|
|
646
|
+
public connectAction(...args: SignalConnection[]) {
|
|
647
|
+
for (const connection of args) {
|
|
648
|
+
this.signalConnections.add(connection);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Checks if this entity can collide with another entity
|
|
654
|
+
*/
|
|
655
|
+
public canCollideWithEntity(entity: Entity): boolean {
|
|
656
|
+
// Check if the entities can collide based on their collision layers and masks
|
|
657
|
+
// A collision occurs when (A.layer & B.mask) !== 0 && (B.layer & A.mask) !== 0
|
|
658
|
+
return (this.collisionLayer & entity.collisionMask) !== 0 && (entity.collisionLayer & this.collisionMask) !== 0;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Sets the collision layer for this entity
|
|
663
|
+
*
|
|
664
|
+
* @param layer The collision layer or layers (can be combined with bitwise OR)
|
|
665
|
+
*/
|
|
666
|
+
public setCollisionLayer(layer: number): void {
|
|
667
|
+
this.collisionLayer = layer;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Adds the specified layers to this entity's collision layer
|
|
672
|
+
*
|
|
673
|
+
* @param layers The layers to add (can be combined with bitwise OR)
|
|
674
|
+
*/
|
|
675
|
+
public addCollisionLayer(layers: number): void {
|
|
676
|
+
this.collisionLayer |= layers;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Removes the specified layers from this entity's collision layer
|
|
681
|
+
*
|
|
682
|
+
* @param layers The layers to remove (can be combined with bitwise OR)
|
|
683
|
+
*/
|
|
684
|
+
public removeCollisionLayer(layers: number): void {
|
|
685
|
+
this.collisionLayer &= ~layers;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Sets the collision mask for this entity
|
|
690
|
+
*
|
|
691
|
+
* @param mask The collision mask (can be combined with bitwise OR)
|
|
692
|
+
*/
|
|
693
|
+
public setCollisionMask(...mask: number[]): void {
|
|
694
|
+
this.collisionMask = this.physics.createCollisionMask(...mask);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Adds the specified layers to this entity's collision mask
|
|
699
|
+
*
|
|
700
|
+
* @param layers The layers to add to the mask (can be combined with bitwise OR)
|
|
701
|
+
*/
|
|
702
|
+
public addCollisionMask(layers: number): void {
|
|
703
|
+
this.collisionMask |= layers;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Removes the specified layers from this entity's collision mask
|
|
708
|
+
*
|
|
709
|
+
* @param layers The layers to remove from the mask (can be combined with bitwise OR)
|
|
710
|
+
*/
|
|
711
|
+
public removeCollisionMask(layers: number): void {
|
|
712
|
+
this.collisionMask &= ~layers;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Checks if this entity belongs to a specific collision layer
|
|
717
|
+
*
|
|
718
|
+
* @param layer The layer to check
|
|
719
|
+
* @returns True if the entity belongs to the specified layer
|
|
720
|
+
*
|
|
721
|
+
* @example
|
|
722
|
+
* ```typescript
|
|
723
|
+
* // Check if entity is on the PLAYER layer
|
|
724
|
+
* if (entity.hasCollisionLayer(CollisionLayer.PLAYER)) {
|
|
725
|
+
* console.log('Entity is a player');
|
|
726
|
+
* }
|
|
727
|
+
*
|
|
728
|
+
* // Check if entity is on a custom layer
|
|
729
|
+
* const WATER_LAYER = CollisionLayers.createLayer(0);
|
|
730
|
+
* if (entity.hasCollisionLayer(WATER_LAYER)) {
|
|
731
|
+
* console.log('Entity is water');
|
|
732
|
+
* }
|
|
733
|
+
* ```
|
|
734
|
+
*/
|
|
735
|
+
public hasCollisionLayer(layer: number): boolean {
|
|
736
|
+
return (this.collisionLayer & layer) !== 0;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Checks if this entity can collide with a specific collision layer
|
|
741
|
+
*
|
|
742
|
+
* @param layer The layer to check
|
|
743
|
+
* @returns True if the entity can collide with the specified layer
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```typescript
|
|
747
|
+
* // Check if entity can collide with players
|
|
748
|
+
* if (entity.canCollideWithLayer(CollisionLayer.PLAYER)) {
|
|
749
|
+
* console.log('Entity can collide with players');
|
|
750
|
+
* }
|
|
751
|
+
*
|
|
752
|
+
* // Check if entity can collide with a custom layer
|
|
753
|
+
* const WATER_LAYER = CollisionLayers.createLayer(0);
|
|
754
|
+
* if (entity.canCollideWithLayer(WATER_LAYER)) {
|
|
755
|
+
* console.log('Entity can collide with water');
|
|
756
|
+
* }
|
|
757
|
+
* ```
|
|
758
|
+
*/
|
|
759
|
+
public canCollideWithLayer(layer: number): boolean {
|
|
760
|
+
return (this.collisionMask & layer) !== 0;
|
|
761
|
+
}
|
|
762
|
+
}
|