@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/Group.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { Actor } from './Actor';
|
|
2
|
+
import { Entity } from './Entity';
|
|
3
|
+
import { Sensor } from './Sensor';
|
|
4
|
+
import { Solid } from './Solid';
|
|
5
|
+
import { EntityData, PhysicsEntityType } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A container for managing collections of physics entities that move together.
|
|
9
|
+
* Groups are useful for creating compound objects, moving platforms with riders,
|
|
10
|
+
* and other scenarios where multiple entities need to move as one unit.
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Maintains relative positioning of child entities
|
|
14
|
+
* - Supports actors, solids, and sensors as children
|
|
15
|
+
* - Preserves world positions when adding entities
|
|
16
|
+
* - Provides type-safe access to children by entity type
|
|
17
|
+
*
|
|
18
|
+
* @typeParam T - Application type, defaults to base Application
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* // Create a moving platform with obstacles
|
|
23
|
+
* const platformGroup = physics.createGroup({
|
|
24
|
+
* type: 'MovingPlatform',
|
|
25
|
+
* position: [100, 300]
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Add platform and spikes
|
|
29
|
+
* const platform = physics.createSolid({
|
|
30
|
+
* type: 'Platform',
|
|
31
|
+
* size: [200, 32]
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* const spikes = physics.createSensor({
|
|
35
|
+
* type: 'Spikes',
|
|
36
|
+
* position: [0, -32],
|
|
37
|
+
* size: [200, 32]
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* platformGroup.add(platform);
|
|
41
|
+
* platformGroup.add(spikes);
|
|
42
|
+
*
|
|
43
|
+
* // Move the entire group
|
|
44
|
+
* gsap.to(platformGroup, {
|
|
45
|
+
* x: 500,
|
|
46
|
+
* duration: 2,
|
|
47
|
+
* yoyo: true,
|
|
48
|
+
* repeat: -1
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export class Group<D extends EntityData = EntityData> extends Entity<D> {
|
|
53
|
+
protected _visible: boolean = true;
|
|
54
|
+
|
|
55
|
+
public entityType: PhysicsEntityType = 'Group';
|
|
56
|
+
/** Map of child entities to their relative offsets from the group's position */
|
|
57
|
+
private childOffsets: Map<Entity, { x: number; y: number }> = new Map();
|
|
58
|
+
/** Whether this group's position is static (not affected by physics) */
|
|
59
|
+
public isStatic: boolean = true;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Gets the relative offset of a child entity from the group's position.
|
|
63
|
+
* @param entity - The child entity to get the offset for
|
|
64
|
+
* @returns The relative {x, y} offset of the entity
|
|
65
|
+
*/
|
|
66
|
+
public getChildOffset(entity: Entity): { x: number; y: number } {
|
|
67
|
+
return this.childOffsets.get(entity) ?? { x: 0, y: 0 };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
set visible(value: boolean) {
|
|
71
|
+
this.system.getEntitiesInGroup(this).forEach((entity) => {
|
|
72
|
+
entity.view.visible = value;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get visible(): boolean {
|
|
77
|
+
return this._visible;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Sets the group's X position, affecting all child entities.
|
|
82
|
+
*/
|
|
83
|
+
set x(value: number) {
|
|
84
|
+
this._x = value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get x(): number {
|
|
88
|
+
return this._x;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Sets the group's Y position, affecting all child entities.
|
|
93
|
+
*/
|
|
94
|
+
set y(value: number) {
|
|
95
|
+
this._y = value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get y(): number {
|
|
99
|
+
return this._y;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Add an entity to this container
|
|
104
|
+
* @param entity The entity to add
|
|
105
|
+
* @param preserveWorldPosition If true, the entity's world position will be preserved
|
|
106
|
+
*/
|
|
107
|
+
public add(entity: Entity, offset: { x: number; y: number } = { x: 0, y: 0 }): Group {
|
|
108
|
+
entity.setGroup(this, offset);
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Remove an entity from this container
|
|
114
|
+
*/
|
|
115
|
+
public remove(entity: Entity): Group {
|
|
116
|
+
entity.setGroup(null);
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Move the container and all its children
|
|
122
|
+
*/
|
|
123
|
+
public move(x: number, y: number): void {
|
|
124
|
+
// Update container position
|
|
125
|
+
this._x += x;
|
|
126
|
+
this._y += y;
|
|
127
|
+
|
|
128
|
+
this.updateView();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Force move the container and all its children to an absolute position
|
|
133
|
+
*/
|
|
134
|
+
public moveTo(x: number, y: number): void {
|
|
135
|
+
const dx = x - this.x;
|
|
136
|
+
const dy = y - this.y;
|
|
137
|
+
this.move(dx, dy);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get all children of a specific type
|
|
142
|
+
*/
|
|
143
|
+
public getChildrenByType<E extends Entity>(type: PhysicsEntityType): E[] {
|
|
144
|
+
return this.system
|
|
145
|
+
.getEntitiesInGroup(this)
|
|
146
|
+
.filter((child): child is E => child.entityType === type || child.type === type);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get all actors in this container
|
|
151
|
+
*/
|
|
152
|
+
public getActors(): Actor[] {
|
|
153
|
+
return this.getChildrenByType('Actor');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get all solids in this container
|
|
158
|
+
*/
|
|
159
|
+
public getSolids(): Solid[] {
|
|
160
|
+
return this.getChildrenByType('Solid');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get all sensors in this container
|
|
165
|
+
*/
|
|
166
|
+
public getSensors(): Sensor[] {
|
|
167
|
+
return this.getChildrenByType('Sensor');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get all groups in this container
|
|
172
|
+
*/
|
|
173
|
+
public getGroups(): Group[] {
|
|
174
|
+
return this.getChildrenByType('Group');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public destroy(): void {
|
|
178
|
+
this.system.removeGroup(this);
|
|
179
|
+
super.destroy();
|
|
180
|
+
}
|
|
181
|
+
}
|
package/src/Sensor.ts
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { Actor } from './Actor';
|
|
2
|
+
import { Entity } from './Entity';
|
|
3
|
+
import { Solid } from './Solid';
|
|
4
|
+
import { EntityData, PhysicsEntityConfig, PhysicsEntityType, SensorOverlap, Vector2 } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A trigger zone that can detect overlaps with actors.
|
|
8
|
+
* Sensors are typically used for collectibles, triggers, and detection zones.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Overlap detection with specific actor types
|
|
12
|
+
* - Optional gravity and movement
|
|
13
|
+
* - Can be static or dynamic
|
|
14
|
+
* - Callbacks for enter/exit events
|
|
15
|
+
*
|
|
16
|
+
* @typeParam T - Application type, defaults to base Application
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Create a coin pickup sensor
|
|
21
|
+
* class Coin extends Sensor {
|
|
22
|
+
* constructor() {
|
|
23
|
+
* super({
|
|
24
|
+
* type: 'Coin',
|
|
25
|
+
* position: [100, 100],
|
|
26
|
+
* size: [32, 32],
|
|
27
|
+
* view: coinSprite
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Only detect overlaps with player
|
|
31
|
+
* this.collidableTypes = ['Player'];
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* // Called when a player enters the coin
|
|
35
|
+
* onActorEnter(actor: Actor) {
|
|
36
|
+
* if (actor.type === 'Player') {
|
|
37
|
+
* increaseScore(10);
|
|
38
|
+
* this.physics.removeSensor(this);
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* // Create a damage zone
|
|
44
|
+
* class Spikes extends Sensor {
|
|
45
|
+
* constructor() {
|
|
46
|
+
* super({
|
|
47
|
+
* type: 'Spikes',
|
|
48
|
+
* position: [300, 500],
|
|
49
|
+
* size: [100, 32],
|
|
50
|
+
* view: spikesSprite
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* this.collidableTypes = ['Player', 'Enemy'];
|
|
54
|
+
* this.isStatic = true; // Don't move or fall
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* onActorEnter(actor: Actor) {
|
|
58
|
+
* if (actor.type === 'Player') {
|
|
59
|
+
* actor.damage(10);
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export class Sensor<D extends EntityData = EntityData> extends Entity<D> {
|
|
66
|
+
public readonly entityType: PhysicsEntityType = 'Sensor';
|
|
67
|
+
/** Whether this sensor should be removed when culled */
|
|
68
|
+
public shouldRemoveOnCull = false;
|
|
69
|
+
|
|
70
|
+
/** List of actor types this sensor can detect */
|
|
71
|
+
public collidableTypes: string[] = [];
|
|
72
|
+
|
|
73
|
+
/** Current velocity in pixels per second */
|
|
74
|
+
public velocity: Vector2 = { x: 0, y: 0 };
|
|
75
|
+
|
|
76
|
+
/** Whether this sensor should stay in place */
|
|
77
|
+
public isStatic: boolean = false;
|
|
78
|
+
|
|
79
|
+
/** Set of actors currently overlapping this sensor */
|
|
80
|
+
private overlappingActors: Set<Actor> = new Set();
|
|
81
|
+
|
|
82
|
+
/** Cache for isRidingSolid check */
|
|
83
|
+
private _isRidingSolidCache: boolean | null = null;
|
|
84
|
+
|
|
85
|
+
private _currentSensorOverlaps = new Set<SensorOverlap>();
|
|
86
|
+
private _currentOverlaps = new Set<Actor>();
|
|
87
|
+
|
|
88
|
+
public setPosition(x: number, y: number): void {
|
|
89
|
+
if (this.isStatic) {
|
|
90
|
+
this._x = x;
|
|
91
|
+
this._y = y;
|
|
92
|
+
this._xRemainder = 0;
|
|
93
|
+
this._yRemainder = 0;
|
|
94
|
+
this.updateView();
|
|
95
|
+
this.checkActorOverlaps();
|
|
96
|
+
} else {
|
|
97
|
+
super.setPosition(x, y);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
set x(value: number) {
|
|
102
|
+
super.x = value;
|
|
103
|
+
if (this.isStatic) {
|
|
104
|
+
this._xRemainder = 0;
|
|
105
|
+
this.updateView();
|
|
106
|
+
this.checkActorOverlaps();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get x(): number {
|
|
111
|
+
return super.x;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
set y(value: number) {
|
|
115
|
+
super.y = value;
|
|
116
|
+
if (this.isStatic) {
|
|
117
|
+
this._yRemainder = 0;
|
|
118
|
+
this.updateView();
|
|
119
|
+
this.checkActorOverlaps();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get y(): number {
|
|
124
|
+
return super.y;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Initializes or reinitializes the sensor with new configuration.
|
|
129
|
+
*
|
|
130
|
+
* @param config - Configuration for the sensor
|
|
131
|
+
*/
|
|
132
|
+
public init(config: PhysicsEntityConfig<D>): void {
|
|
133
|
+
super.init(config);
|
|
134
|
+
if (!this.velocity) {
|
|
135
|
+
this.velocity = { x: 0, y: 0 };
|
|
136
|
+
}
|
|
137
|
+
this.velocity.x = 0;
|
|
138
|
+
this.velocity.y = 0;
|
|
139
|
+
if (!this.overlappingActors) {
|
|
140
|
+
this.overlappingActors = new Set();
|
|
141
|
+
}
|
|
142
|
+
this._isRidingSolidCache = null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Checks if this sensor is riding the given solid.
|
|
147
|
+
* Takes into account gravity direction for proper riding detection.
|
|
148
|
+
*
|
|
149
|
+
* @param solid - The solid to check against
|
|
150
|
+
* @returns True if riding the solid
|
|
151
|
+
*/
|
|
152
|
+
public isRiding(solid: Solid): boolean {
|
|
153
|
+
const gravityDirection = Math.sign(this.system.gravity);
|
|
154
|
+
if (gravityDirection > 0) {
|
|
155
|
+
// Normal gravity - check if we're on top of the solid
|
|
156
|
+
const sensorBottom = this.y + this.height;
|
|
157
|
+
const onTop = Math.abs(sensorBottom - solid.y) <= 1;
|
|
158
|
+
const overlap = this.x + this.width > solid.x && this.x < solid.x + solid.width;
|
|
159
|
+
return onTop && overlap;
|
|
160
|
+
} else {
|
|
161
|
+
// Reversed gravity - check if we're on bottom of the solid
|
|
162
|
+
const sensorTop = this.y;
|
|
163
|
+
const onBottom = Math.abs(sensorTop - (solid.y + solid.height)) <= 1;
|
|
164
|
+
const overlap = this.x + this.width > solid.x && this.x < solid.x + solid.width;
|
|
165
|
+
return onBottom && overlap;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Checks if this sensor is riding any solid.
|
|
171
|
+
* Uses caching to optimize multiple checks per frame.
|
|
172
|
+
*
|
|
173
|
+
* @returns True if riding any solid
|
|
174
|
+
*/
|
|
175
|
+
public isRidingSolid(): boolean {
|
|
176
|
+
// Return cached value if available
|
|
177
|
+
if (this._isRidingSolidCache !== null) {
|
|
178
|
+
return this._isRidingSolidCache;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Calculate and cache the result
|
|
182
|
+
const solids = this.getSolidsAt(this.x, this.system.gravity > 0 ? this.y + 1 : this.y - 1);
|
|
183
|
+
this._isRidingSolidCache = solids.some((solid) => this.isRiding(solid));
|
|
184
|
+
return this._isRidingSolidCache;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Force moves the sensor to a new position, ignoring static state and collisions.
|
|
189
|
+
*
|
|
190
|
+
* @param x - New X position
|
|
191
|
+
* @param y - New Y position
|
|
192
|
+
*/
|
|
193
|
+
public moveStatic(x: number, y: number): void {
|
|
194
|
+
this._x = x;
|
|
195
|
+
this._y = y;
|
|
196
|
+
this._xRemainder = 0;
|
|
197
|
+
this._yRemainder = 0;
|
|
198
|
+
this.updateView();
|
|
199
|
+
this.checkActorOverlaps();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Moves the sensor horizontally, passing through solids.
|
|
204
|
+
*
|
|
205
|
+
* @param amount - Distance to move in pixels
|
|
206
|
+
*/
|
|
207
|
+
public moveX(amount: number): void {
|
|
208
|
+
if (this.isStatic) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this._xRemainder += amount;
|
|
213
|
+
const move = Math.round(this._xRemainder);
|
|
214
|
+
|
|
215
|
+
if (move !== 0) {
|
|
216
|
+
this._xRemainder -= move;
|
|
217
|
+
const sign = Math.sign(move);
|
|
218
|
+
let remaining = Math.abs(move);
|
|
219
|
+
while (remaining > 0) {
|
|
220
|
+
const step = sign;
|
|
221
|
+
const nextX = this.x + step;
|
|
222
|
+
|
|
223
|
+
// Check for collision with any solid
|
|
224
|
+
let collided = false;
|
|
225
|
+
for (const solid of this.getSolidsAt(nextX, this.y)) {
|
|
226
|
+
if (solid.canCollide) {
|
|
227
|
+
collided = true;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!collided) {
|
|
233
|
+
this._x = nextX;
|
|
234
|
+
remaining--;
|
|
235
|
+
this.updateView();
|
|
236
|
+
this.checkActorOverlaps();
|
|
237
|
+
} else {
|
|
238
|
+
// Stop horizontal movement when hitting a solid
|
|
239
|
+
this.velocity.x = 0;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Moves the sensor vertically, colliding with solids for riding.
|
|
248
|
+
*
|
|
249
|
+
* @param amount - Distance to move in pixels
|
|
250
|
+
*/
|
|
251
|
+
public moveY(amount: number): void {
|
|
252
|
+
if (this.isStatic) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this._yRemainder += amount;
|
|
257
|
+
const move = Math.round(this._yRemainder);
|
|
258
|
+
|
|
259
|
+
if (move !== 0) {
|
|
260
|
+
this._yRemainder -= move;
|
|
261
|
+
const sign = Math.sign(move);
|
|
262
|
+
|
|
263
|
+
let remaining = Math.abs(move);
|
|
264
|
+
while (remaining > 0) {
|
|
265
|
+
const step = sign;
|
|
266
|
+
const nextY = this.y + step;
|
|
267
|
+
|
|
268
|
+
// Check for collision with any solid
|
|
269
|
+
let collided = false;
|
|
270
|
+
// Only check collisions when moving in the direction of gravity
|
|
271
|
+
if (Math.sign(this.system.gravity) === sign) {
|
|
272
|
+
for (const solid of this.getSolidsAt(this.x, nextY)) {
|
|
273
|
+
if (solid.canCollide) {
|
|
274
|
+
collided = true;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!collided) {
|
|
281
|
+
this._y = nextY;
|
|
282
|
+
remaining--;
|
|
283
|
+
this.updateView();
|
|
284
|
+
this.checkActorOverlaps();
|
|
285
|
+
} else {
|
|
286
|
+
// Stop vertical movement when landing on a solid
|
|
287
|
+
this.velocity.y = 0;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Updates the sensor's position and checks for overlapping actors.
|
|
296
|
+
*
|
|
297
|
+
* @param deltaTime - Delta time in seconds
|
|
298
|
+
*/
|
|
299
|
+
public update(deltaTime: number): void {
|
|
300
|
+
// Reset the cache at the start of each update
|
|
301
|
+
this._isRidingSolidCache = null;
|
|
302
|
+
|
|
303
|
+
if (!this.active) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Only apply gravity if not static and not riding a solid
|
|
308
|
+
if (!this.isStatic && !this.isRidingSolid()) {
|
|
309
|
+
this.velocity.y += this.system.gravity * deltaTime;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Move
|
|
313
|
+
if (this.velocity.x !== 0) {
|
|
314
|
+
this.moveX(this.velocity.x * deltaTime);
|
|
315
|
+
}
|
|
316
|
+
if (this.velocity.y !== 0) {
|
|
317
|
+
this.moveY(this.velocity.y * deltaTime);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Checks for overlapping actors and triggers callbacks.
|
|
323
|
+
*
|
|
324
|
+
* @returns Set of current overlaps
|
|
325
|
+
*/
|
|
326
|
+
public checkActorOverlaps(): Set<SensorOverlap> {
|
|
327
|
+
// Skip if sensor has no collision mask or is inactive
|
|
328
|
+
if (this.collisionMask === 0 || !this.active) {
|
|
329
|
+
return new Set();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
this._currentSensorOverlaps.clear();
|
|
333
|
+
this._currentOverlaps.clear();
|
|
334
|
+
|
|
335
|
+
// Get all actors at current position - use spatial filtering first
|
|
336
|
+
// Only get actors that are potentially in the same grid cells
|
|
337
|
+
|
|
338
|
+
// Cache collision layer and mask for faster access
|
|
339
|
+
const sensorLayer = this.collisionLayer;
|
|
340
|
+
const sensorMask = this.collisionMask;
|
|
341
|
+
|
|
342
|
+
// Get actors by type, but only process those that could be nearby
|
|
343
|
+
const nearbyActors = this.system.getActorsByType(this.collidableTypes);
|
|
344
|
+
|
|
345
|
+
for (const actor of nearbyActors) {
|
|
346
|
+
// Skip inactive actors
|
|
347
|
+
if (!actor.active) continue;
|
|
348
|
+
|
|
349
|
+
// Fast collision layer check
|
|
350
|
+
if ((sensorLayer & actor.collisionMask) === 0 || (actor.collisionLayer & sensorMask) === 0) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Fast AABB check before detailed overlap
|
|
355
|
+
if (this.system.aabbOverlap(this, actor)) {
|
|
356
|
+
this._currentOverlaps.add(actor);
|
|
357
|
+
if (!this.overlappingActors.has(actor)) {
|
|
358
|
+
// New overlap
|
|
359
|
+
this._currentSensorOverlaps.add({
|
|
360
|
+
actor,
|
|
361
|
+
sensor: this,
|
|
362
|
+
type: `${actor.type}|${this.type}`,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
this.onActorEnter(actor);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Check for actors that are no longer overlapping
|
|
371
|
+
for (const actor of this.overlappingActors) {
|
|
372
|
+
if (!this._currentOverlaps.has(actor)) {
|
|
373
|
+
this.onActorExit(actor);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Swap the sets to avoid unnecessary clear and forEach operations
|
|
378
|
+
const temp = this.overlappingActors;
|
|
379
|
+
this.overlappingActors = this._currentOverlaps;
|
|
380
|
+
this._currentOverlaps = temp;
|
|
381
|
+
this._currentOverlaps.clear();
|
|
382
|
+
|
|
383
|
+
return this._currentSensorOverlaps;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
public reset(): void {
|
|
387
|
+
super.reset();
|
|
388
|
+
this._currentSensorOverlaps.clear();
|
|
389
|
+
this._currentOverlaps.clear();
|
|
390
|
+
this._isRidingSolidCache = null;
|
|
391
|
+
this.velocity = { x: 0, y: 0 };
|
|
392
|
+
this.overlappingActors = new Set();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Called when an actor starts overlapping with this sensor.
|
|
397
|
+
* Override this to handle overlap start events.
|
|
398
|
+
*
|
|
399
|
+
* @param actor - The actor that entered
|
|
400
|
+
*/
|
|
401
|
+
public onActorEnter<A extends Actor = Actor>(actor: A): void {
|
|
402
|
+
// Override in subclass
|
|
403
|
+
void actor;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Called when an actor stops overlapping with this sensor.
|
|
408
|
+
* Override this to handle overlap end events.
|
|
409
|
+
*
|
|
410
|
+
* @param actor - The actor that exited
|
|
411
|
+
*/
|
|
412
|
+
public onActorExit<A extends Actor = Actor>(actor: A): void {
|
|
413
|
+
// Override in subclass
|
|
414
|
+
void actor;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Gets all solids at the specified position.
|
|
419
|
+
*
|
|
420
|
+
* @param x - X position to check
|
|
421
|
+
* @param y - Y position to check
|
|
422
|
+
* @returns Array of solids at the position
|
|
423
|
+
*/
|
|
424
|
+
protected getSolidsAt(x: number, y: number): Solid[] {
|
|
425
|
+
return this.system.getSolidsAt(x, y, this);
|
|
426
|
+
}
|
|
427
|
+
}
|