@rpgjs/common 4.3.0 → 5.0.0-alpha.1
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/dist/Physic.d.ts +619 -0
- package/dist/Player.d.ts +198 -0
- package/{lib → dist}/Utils.d.ts +19 -2
- package/dist/database/Item.d.ts +10 -0
- package/dist/database/index.d.ts +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +16741 -0
- package/dist/index.js.map +1 -0
- package/dist/modules.d.ts +92 -0
- package/dist/movement/MovementManager.d.ts +84 -0
- package/dist/movement/MovementStrategy.d.ts +39 -0
- package/dist/movement/index.d.ts +12 -0
- package/dist/movement/strategies/CompositeMovement.d.ts +76 -0
- package/dist/movement/strategies/Dash.d.ts +52 -0
- package/dist/movement/strategies/IceMovement.d.ts +87 -0
- package/dist/movement/strategies/Knockback.d.ts +50 -0
- package/dist/movement/strategies/LinearMove.d.ts +43 -0
- package/dist/movement/strategies/LinearRepulsion.d.ts +55 -0
- package/dist/movement/strategies/Oscillate.d.ts +60 -0
- package/dist/movement/strategies/PathFollow.d.ts +78 -0
- package/dist/movement/strategies/ProjectileMovement.d.ts +138 -0
- package/dist/movement/strategies/SeekAvoid.d.ts +27 -0
- package/dist/rooms/Map.d.ts +109 -0
- package/dist/services/updateMap.d.ts +7 -0
- package/package.json +17 -17
- package/src/Physic.ts +1644 -0
- package/src/Player.ts +262 -26
- package/src/{gui/PrebuiltGui.ts → PrebuiltGui.ts} +1 -1
- package/src/Utils.ts +184 -123
- package/src/database/Item.ts +19 -0
- package/src/database/index.ts +1 -0
- package/src/index.ts +9 -25
- package/src/modules.ts +230 -0
- package/src/movement/MovementManager.ts +142 -0
- package/src/movement/MovementStrategy.ts +42 -0
- package/src/movement/index.ts +15 -0
- package/src/movement/strategies/CompositeMovement.ts +173 -0
- package/src/movement/strategies/Dash.ts +82 -0
- package/src/movement/strategies/IceMovement.ts +158 -0
- package/src/movement/strategies/Knockback.ts +81 -0
- package/src/movement/strategies/LinearMove.ts +58 -0
- package/src/movement/strategies/LinearRepulsion.ts +128 -0
- package/src/movement/strategies/Oscillate.ts +144 -0
- package/src/movement/strategies/PathFollow.ts +156 -0
- package/src/movement/strategies/ProjectileMovement.ts +322 -0
- package/src/movement/strategies/SeekAvoid.ts +123 -0
- package/src/rooms/Map.ts +272 -0
- package/src/services/updateMap.ts +9 -0
- package/tests/physic.spec.ts +454 -0
- package/tsconfig.json +8 -3
- package/vite.config.ts +21 -0
- package/CHANGELOG.md +0 -160
- package/LICENSE +0 -19
- package/browser/manifest.json +0 -7
- package/browser/rpg.common.js +0 -11357
- package/browser/rpg.common.umd.cjs +0 -11358
- package/lib/AbstractObject.d.ts +0 -322
- package/lib/AbstractObject.js +0 -872
- package/lib/AbstractObject.js.map +0 -1
- package/lib/Color.d.ts +0 -1
- package/lib/Color.js +0 -25
- package/lib/Color.js.map +0 -1
- package/lib/DefaultInput.d.ts +0 -2
- package/lib/DefaultInput.js +0 -26
- package/lib/DefaultInput.js.map +0 -1
- package/lib/Event.d.ts +0 -3
- package/lib/Event.js +0 -4
- package/lib/Event.js.map +0 -1
- package/lib/EventEmitter.d.ts +0 -10
- package/lib/EventEmitter.js +0 -61
- package/lib/EventEmitter.js.map +0 -1
- package/lib/Game.d.ts +0 -28
- package/lib/Game.js +0 -127
- package/lib/Game.js.map +0 -1
- package/lib/Hit.d.ts +0 -16
- package/lib/Hit.js +0 -65
- package/lib/Hit.js.map +0 -1
- package/lib/Inject.d.ts +0 -9
- package/lib/Inject.js +0 -17
- package/lib/Inject.js.map +0 -1
- package/lib/Logger.d.ts +0 -2
- package/lib/Logger.js +0 -7
- package/lib/Logger.js.map +0 -1
- package/lib/Map.d.ts +0 -174
- package/lib/Map.js +0 -263
- package/lib/Map.js.map +0 -1
- package/lib/Module.d.ts +0 -16
- package/lib/Module.js +0 -139
- package/lib/Module.js.map +0 -1
- package/lib/Player.d.ts +0 -26
- package/lib/Player.js +0 -19
- package/lib/Player.js.map +0 -1
- package/lib/Plugin.d.ts +0 -67
- package/lib/Plugin.js +0 -92
- package/lib/Plugin.js.map +0 -1
- package/lib/Scheduler.d.ts +0 -26
- package/lib/Scheduler.js +0 -90
- package/lib/Scheduler.js.map +0 -1
- package/lib/Shape.d.ts +0 -127
- package/lib/Shape.js +0 -261
- package/lib/Shape.js.map +0 -1
- package/lib/Utils.js +0 -181
- package/lib/Utils.js.map +0 -1
- package/lib/Vector2d.d.ts +0 -20
- package/lib/Vector2d.js +0 -63
- package/lib/Vector2d.js.map +0 -1
- package/lib/VirtualGrid.d.ts +0 -26
- package/lib/VirtualGrid.js +0 -68
- package/lib/VirtualGrid.js.map +0 -1
- package/lib/Worker.d.ts +0 -7
- package/lib/Worker.js +0 -13
- package/lib/Worker.js.map +0 -1
- package/lib/WorldMaps.d.ts +0 -105
- package/lib/WorldMaps.js +0 -184
- package/lib/WorldMaps.js.map +0 -1
- package/lib/gui/PrebuiltGui.js +0 -29
- package/lib/gui/PrebuiltGui.js.map +0 -1
- package/lib/index.d.ts +0 -25
- package/lib/index.js +0 -26
- package/lib/index.js.map +0 -1
- package/lib/transports/io.d.ts +0 -22
- package/lib/transports/io.js +0 -82
- package/lib/transports/io.js.map +0 -1
- package/lib/workers/move.d.ts +0 -1
- package/lib/workers/move.js +0 -57
- package/lib/workers/move.js.map +0 -1
- package/rpg.toml +0 -11
- package/src/AbstractObject.ts +0 -973
- package/src/Color.ts +0 -29
- package/src/DefaultInput.ts +0 -26
- package/src/Event.ts +0 -3
- package/src/EventEmitter.ts +0 -65
- package/src/Game.ts +0 -159
- package/src/Hit.ts +0 -70
- package/src/Inject.ts +0 -22
- package/src/Logger.ts +0 -7
- package/src/Map.ts +0 -335
- package/src/Module.ts +0 -144
- package/src/Plugin.ts +0 -100
- package/src/Scheduler.ts +0 -95
- package/src/Shape.ts +0 -302
- package/src/Vector2d.ts +0 -70
- package/src/VirtualGrid.ts +0 -78
- package/src/Worker.ts +0 -17
- package/src/WorldMaps.ts +0 -204
- package/src/transports/io.ts +0 -91
- package/src/workers/move.ts +0 -61
- /package/{lib/gui → dist}/PrebuiltGui.d.ts +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as Matter from 'matter-js';
|
|
2
|
+
import { MovementStrategy } from '../MovementStrategy';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Applies constant velocity movement in a specified direction
|
|
6
|
+
*
|
|
7
|
+
* Used for simple linear movement at a constant speed
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Move entity right at 5 pixels per frame
|
|
12
|
+
* movementManager.add('entity1', new LinearMove(5, 0));
|
|
13
|
+
*
|
|
14
|
+
* // Move diagonally
|
|
15
|
+
* movementManager.add('entity1', new LinearMove(3, 3));
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export class LinearMove implements MovementStrategy {
|
|
19
|
+
/**
|
|
20
|
+
* Create a linear movement
|
|
21
|
+
*
|
|
22
|
+
* @param vx - X velocity component
|
|
23
|
+
* @param vy - Y velocity component
|
|
24
|
+
* @param duration - Optional duration in milliseconds (if not set, movement continues indefinitely)
|
|
25
|
+
*/
|
|
26
|
+
constructor(
|
|
27
|
+
private vx: number,
|
|
28
|
+
private vy: number,
|
|
29
|
+
private duration?: number
|
|
30
|
+
) {
|
|
31
|
+
this.elapsed = 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private elapsed: number;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Apply velocity to the body
|
|
38
|
+
*
|
|
39
|
+
* @param body - Matter.js body to move
|
|
40
|
+
* @param dt - Time delta in milliseconds
|
|
41
|
+
*/
|
|
42
|
+
update(body: Matter.Body, dt: number): void {
|
|
43
|
+
if (this.duration !== undefined) {
|
|
44
|
+
this.elapsed += dt;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Matter.Body.setVelocity(body, { x: this.vx, y: this.vy });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if movement duration has elapsed
|
|
52
|
+
*
|
|
53
|
+
* @returns True if movement should stop
|
|
54
|
+
*/
|
|
55
|
+
isFinished(): boolean {
|
|
56
|
+
return this.duration !== undefined && this.elapsed >= this.duration;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as Matter from 'matter-js';
|
|
2
|
+
import { RpgCommonPhysic } from '../../Physic';
|
|
3
|
+
import { MovementStrategy } from '../MovementStrategy';
|
|
4
|
+
|
|
5
|
+
/** Utilitaires Vec2 pour éviter des new() à chaque frame */
|
|
6
|
+
const vTmp1 = Matter.Vector.create();
|
|
7
|
+
const vTmp2 = Matter.Vector.create();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Implements a seek behavior with linear obstacle avoidance
|
|
11
|
+
*
|
|
12
|
+
* Similar to SeekAvoid but uses a linear repulsion model instead of inverse square.
|
|
13
|
+
* This creates a smoother avoidance field with gradual transitions.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // Make enemy follow player with smooth avoidance
|
|
18
|
+
* const playerPosition = () => Matter.Vector.create(player.x(), player.y());
|
|
19
|
+
* movementManager.add('enemy1', new LinearRepulsion(physics, playerPosition, 3, 50, 5));
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class LinearRepulsion implements MovementStrategy {
|
|
23
|
+
private repulseRadius2: number;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a seeking movement with linear obstacle avoidance
|
|
27
|
+
*
|
|
28
|
+
* @param phys - Physics system to query for nearby obstacles
|
|
29
|
+
* @param target - Function returning the current target position
|
|
30
|
+
* @param maxSpeed - Maximum movement speed
|
|
31
|
+
* @param repulseRadius - Radius around entity to check for obstacles
|
|
32
|
+
* @param repulseWeight - Strength of repulsion from obstacles
|
|
33
|
+
*/
|
|
34
|
+
constructor(
|
|
35
|
+
private phys: RpgCommonPhysic,
|
|
36
|
+
private target: () => Matter.Vector,
|
|
37
|
+
private maxSpeed = 2.5,
|
|
38
|
+
private repulseRadius = 32,
|
|
39
|
+
private repulseWeight = 4
|
|
40
|
+
) {
|
|
41
|
+
this.repulseRadius2 = this.repulseRadius * this.repulseRadius;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Update seek and avoid behavior with linear repulsion
|
|
46
|
+
*
|
|
47
|
+
* @param body - Matter.js body to move
|
|
48
|
+
* @param dt - Time delta in milliseconds
|
|
49
|
+
*/
|
|
50
|
+
update(body: Matter.Body, dt: number): void {
|
|
51
|
+
// 1) Attraction (pull) vers la cible actuelle
|
|
52
|
+
Matter.Vector.sub(this.target(), body.position, vTmp1);
|
|
53
|
+
const distance = Matter.Vector.magnitude(vTmp1);
|
|
54
|
+
if (distance > 0.0001) Matter.Vector.mult(vTmp1, 1 / distance, vTmp1);
|
|
55
|
+
|
|
56
|
+
// 2) Répulsion linéaire - force plus douce qui diminue graduellement
|
|
57
|
+
const area = {
|
|
58
|
+
min: { x: body.position.x - this.repulseRadius,
|
|
59
|
+
y: body.position.y - this.repulseRadius },
|
|
60
|
+
max: { x: body.position.x + this.repulseRadius,
|
|
61
|
+
y: body.position.y + this.repulseRadius }
|
|
62
|
+
};
|
|
63
|
+
const nearby = Matter.Query.region(this.phys.getWorld().bodies, area);
|
|
64
|
+
|
|
65
|
+
let pushX = 0, pushY = 0;
|
|
66
|
+
for (const b of nearby) {
|
|
67
|
+
if (b === body || b.isSensor) continue;
|
|
68
|
+
|
|
69
|
+
Matter.Vector.sub(body.position, b.position, vTmp2);
|
|
70
|
+
const d2 = Matter.Vector.magnitudeSquared(vTmp2);
|
|
71
|
+
|
|
72
|
+
if (d2 > this.repulseRadius2) continue; // trop loin, pas de répulsion
|
|
73
|
+
|
|
74
|
+
// Répulsion linéaire: force maximale au centre, nulle au bord
|
|
75
|
+
const d = Math.sqrt(d2);
|
|
76
|
+
const w = this.repulseWeight * (this.repulseRadius - d) / this.repulseRadius;
|
|
77
|
+
|
|
78
|
+
// Normalisation du vecteur de répulsion
|
|
79
|
+
if (d > 0.0001) {
|
|
80
|
+
pushX += vTmp2.x * w / d;
|
|
81
|
+
pushY += vTmp2.y * w / d;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Limiter la norme du push cumulé
|
|
86
|
+
const MAX_PUSH_LEN = this.maxSpeed * 3;
|
|
87
|
+
let pushLen = Math.hypot(pushX, pushY);
|
|
88
|
+
if (pushLen > MAX_PUSH_LEN) {
|
|
89
|
+
pushX = pushX * MAX_PUSH_LEN / pushLen;
|
|
90
|
+
pushY = pushY * MAX_PUSH_LEN / pushLen;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 3) Vitesse désirée = pull + push, puis clamp à maxSpeed
|
|
94
|
+
const desired = Matter.Vector.create(
|
|
95
|
+
vTmp1.x * this.maxSpeed + pushX,
|
|
96
|
+
vTmp1.y * this.maxSpeed + pushY
|
|
97
|
+
);
|
|
98
|
+
const len = Matter.Vector.magnitude(desired);
|
|
99
|
+
if (len > this.maxSpeed) Matter.Vector.mult(desired, this.maxSpeed / len, desired);
|
|
100
|
+
|
|
101
|
+
Matter.Body.setVelocity(body, desired);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Update the target to follow
|
|
106
|
+
*
|
|
107
|
+
* @param newTarget - Function returning the new target position
|
|
108
|
+
*/
|
|
109
|
+
setTarget(newTarget: () => Matter.Vector): void {
|
|
110
|
+
this.target = newTarget;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Update movement parameters
|
|
115
|
+
*
|
|
116
|
+
* @param maxSpeed - New maximum speed
|
|
117
|
+
* @param repulseRadius - New repulsion radius
|
|
118
|
+
* @param repulseWeight - New repulsion weight
|
|
119
|
+
*/
|
|
120
|
+
setParameters(maxSpeed?: number, repulseRadius?: number, repulseWeight?: number): void {
|
|
121
|
+
if (maxSpeed !== undefined) this.maxSpeed = maxSpeed;
|
|
122
|
+
if (repulseRadius !== undefined) {
|
|
123
|
+
this.repulseRadius = repulseRadius;
|
|
124
|
+
this.repulseRadius2 = this.repulseRadius * this.repulseRadius;
|
|
125
|
+
}
|
|
126
|
+
if (repulseWeight !== undefined) this.repulseWeight = repulseWeight;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import * as Matter from 'matter-js';
|
|
2
|
+
import { MovementStrategy } from '../MovementStrategy';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Implements an oscillating movement pattern
|
|
6
|
+
*
|
|
7
|
+
* Entity moves back and forth along a single axis or in a pattern
|
|
8
|
+
* like sine wave, useful for patrols or ambient movements
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // Horizontal oscillation with amplitude 100 and period 3000ms
|
|
13
|
+
* movementManager.add('entity1', new Oscillate({ x: 1, y: 0 }, 100, 3000));
|
|
14
|
+
*
|
|
15
|
+
* // Vertical oscillation
|
|
16
|
+
* movementManager.add('entity1', new Oscillate({ x: 0, y: 1 }, 50, 2000));
|
|
17
|
+
*
|
|
18
|
+
* // Circular oscillation
|
|
19
|
+
* movementManager.add('entity1', new Oscillate({ x: 1, y: 0 }, 100, 4000, 'circular'));
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class Oscillate implements MovementStrategy {
|
|
23
|
+
private elapsed: number = 0;
|
|
24
|
+
private startPosition: { x: number, y: number } = { x: 0, y: 0 };
|
|
25
|
+
private positionSet: boolean = false;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create an oscillating movement
|
|
29
|
+
*
|
|
30
|
+
* @param direction - Primary axis of oscillation (normalized)
|
|
31
|
+
* @param amplitude - Maximum distance from center position
|
|
32
|
+
* @param period - Time in ms for a complete cycle
|
|
33
|
+
* @param type - Oscillation pattern type ('linear', 'sine', 'circular')
|
|
34
|
+
* @param duration - Optional total duration in ms (undefined for infinite)
|
|
35
|
+
*/
|
|
36
|
+
constructor(
|
|
37
|
+
private direction: { x: number, y: number },
|
|
38
|
+
private amplitude: number,
|
|
39
|
+
private period: number,
|
|
40
|
+
private type: 'linear' | 'sine' | 'circular' = 'sine',
|
|
41
|
+
private duration?: number
|
|
42
|
+
) {
|
|
43
|
+
// Normalize direction vector
|
|
44
|
+
const magnitude = Math.sqrt(direction.x * direction.x + direction.y * direction.y);
|
|
45
|
+
if (magnitude !== 0 && magnitude !== 1) {
|
|
46
|
+
this.direction = {
|
|
47
|
+
x: direction.x / magnitude,
|
|
48
|
+
y: direction.y / magnitude
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Apply oscillating movement
|
|
55
|
+
*
|
|
56
|
+
* @param body - Matter.js body to move
|
|
57
|
+
* @param dt - Time delta in milliseconds
|
|
58
|
+
*/
|
|
59
|
+
update(body: Matter.Body, dt: number): void {
|
|
60
|
+
// Record start position on first update
|
|
61
|
+
if (!this.positionSet) {
|
|
62
|
+
this.startPosition = { ...body.position };
|
|
63
|
+
this.positionSet = true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.elapsed += dt;
|
|
67
|
+
|
|
68
|
+
// Calculate oscillation factor (0 to 1 to 0)
|
|
69
|
+
let factor: number;
|
|
70
|
+
let perpFactor: number = 0;
|
|
71
|
+
|
|
72
|
+
// Calculate progress through cycle (0 to 1)
|
|
73
|
+
const cycleProgress = (this.elapsed % this.period) / this.period;
|
|
74
|
+
|
|
75
|
+
switch (this.type) {
|
|
76
|
+
case 'linear':
|
|
77
|
+
// Triangle wave (0->1->0)
|
|
78
|
+
factor = cycleProgress < 0.5
|
|
79
|
+
? cycleProgress * 2
|
|
80
|
+
: (1 - cycleProgress) * 2;
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case 'sine':
|
|
84
|
+
// Sine wave
|
|
85
|
+
factor = Math.sin(cycleProgress * Math.PI * 2) * 0.5 + 0.5;
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case 'circular':
|
|
89
|
+
// Circular motion
|
|
90
|
+
factor = Math.sin(cycleProgress * Math.PI * 2);
|
|
91
|
+
perpFactor = Math.cos(cycleProgress * Math.PI * 2);
|
|
92
|
+
break;
|
|
93
|
+
|
|
94
|
+
default:
|
|
95
|
+
factor = Math.sin(cycleProgress * Math.PI * 2) * 0.5 + 0.5;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Calculate new position
|
|
99
|
+
let newX, newY;
|
|
100
|
+
|
|
101
|
+
if (this.type === 'circular') {
|
|
102
|
+
// For circular, we need both sine and cosine components
|
|
103
|
+
const perpDirection = { x: -this.direction.y, y: this.direction.x };
|
|
104
|
+
|
|
105
|
+
newX = this.startPosition.x +
|
|
106
|
+
(this.direction.x * factor * this.amplitude) +
|
|
107
|
+
(perpDirection.x * perpFactor * this.amplitude);
|
|
108
|
+
|
|
109
|
+
newY = this.startPosition.y +
|
|
110
|
+
(this.direction.y * factor * this.amplitude) +
|
|
111
|
+
(perpDirection.y * perpFactor * this.amplitude);
|
|
112
|
+
} else {
|
|
113
|
+
// Map factor from 0-1 to -1 to 1 range for linear and sine
|
|
114
|
+
const mappedFactor = factor * 2 - 1;
|
|
115
|
+
|
|
116
|
+
newX = this.startPosition.x + (this.direction.x * mappedFactor * this.amplitude);
|
|
117
|
+
newY = this.startPosition.y + (this.direction.y * mappedFactor * this.amplitude);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Calculate velocity based on position difference
|
|
121
|
+
const vx = (newX - body.position.x) / (dt / 1000);
|
|
122
|
+
const vy = (newY - body.position.y) / (dt / 1000);
|
|
123
|
+
|
|
124
|
+
// Apply velocity
|
|
125
|
+
Matter.Body.setVelocity(body, { x: vx, y: vy });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if oscillation duration has elapsed
|
|
130
|
+
*
|
|
131
|
+
* @returns True if movement is finished
|
|
132
|
+
*/
|
|
133
|
+
isFinished(): boolean {
|
|
134
|
+
return this.duration !== undefined && this.elapsed >= this.duration;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Reset the oscillation to start from the current position
|
|
139
|
+
*/
|
|
140
|
+
reset(): void {
|
|
141
|
+
this.elapsed = 0;
|
|
142
|
+
this.positionSet = false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import * as Matter from 'matter-js';
|
|
2
|
+
import { MovementStrategy } from '../MovementStrategy';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Implements a path following movement that navigates through waypoints
|
|
6
|
+
*
|
|
7
|
+
* The entity will follow a sequence of waypoints at a specified speed,
|
|
8
|
+
* with optional looping behavior.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // Create a patrol path for an NPC
|
|
13
|
+
* const waypoints = [
|
|
14
|
+
* { x: 100, y: 100 },
|
|
15
|
+
* { x: 300, y: 100 },
|
|
16
|
+
* { x: 300, y: 300 },
|
|
17
|
+
* { x: 100, y: 300 }
|
|
18
|
+
* ];
|
|
19
|
+
*
|
|
20
|
+
* // Follow path once
|
|
21
|
+
* movementManager.add('npc1', new PathFollow(waypoints, 3));
|
|
22
|
+
*
|
|
23
|
+
* // Loop indefinitely (patrol)
|
|
24
|
+
* movementManager.add('npc1', new PathFollow(waypoints, 3, true));
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class PathFollow implements MovementStrategy {
|
|
28
|
+
private currentWaypoint: number = 0;
|
|
29
|
+
private finished: boolean = false;
|
|
30
|
+
private direction: { x: number, y: number } = { x: 0, y: 0 };
|
|
31
|
+
private waypointThreshold: number = 5; // Distance threshold to consider waypoint reached
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a path following movement
|
|
35
|
+
*
|
|
36
|
+
* @param waypoints - Array of x,y positions to follow
|
|
37
|
+
* @param speed - Movement speed in pixels per frame
|
|
38
|
+
* @param loop - Whether to loop back to start after reaching final waypoint
|
|
39
|
+
* @param pauseAtWaypoints - Optional time in ms to pause at each waypoint
|
|
40
|
+
*/
|
|
41
|
+
constructor(
|
|
42
|
+
private waypoints: Array<{ x: number, y: number }>,
|
|
43
|
+
private speed: number,
|
|
44
|
+
private loop: boolean = false,
|
|
45
|
+
private pauseAtWaypoints: number = 0
|
|
46
|
+
) {
|
|
47
|
+
if (waypoints.length === 0) {
|
|
48
|
+
this.finished = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private pauseTimer: number = 0;
|
|
53
|
+
private isPaused: boolean = false;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Update path following logic
|
|
57
|
+
*
|
|
58
|
+
* @param body - Matter.js body to move
|
|
59
|
+
* @param dt - Time delta in milliseconds
|
|
60
|
+
*/
|
|
61
|
+
update(body: Matter.Body, dt: number): void {
|
|
62
|
+
if (this.finished || this.waypoints.length === 0) {
|
|
63
|
+
Matter.Body.setVelocity(body, { x: 0, y: 0 });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Handle pausing at waypoints
|
|
68
|
+
if (this.isPaused) {
|
|
69
|
+
Matter.Body.setVelocity(body, { x: 0, y: 0 });
|
|
70
|
+
this.pauseTimer += dt;
|
|
71
|
+
|
|
72
|
+
if (this.pauseTimer >= this.pauseAtWaypoints) {
|
|
73
|
+
this.isPaused = false;
|
|
74
|
+
this.pauseTimer = 0;
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const currentTarget = this.waypoints[this.currentWaypoint];
|
|
80
|
+
const distanceX = currentTarget.x - body.position.x;
|
|
81
|
+
const distanceY = currentTarget.y - body.position.y;
|
|
82
|
+
const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
|
|
83
|
+
|
|
84
|
+
// Check if waypoint is reached
|
|
85
|
+
if (distance <= this.waypointThreshold) {
|
|
86
|
+
// Go to next waypoint
|
|
87
|
+
this.currentWaypoint++;
|
|
88
|
+
|
|
89
|
+
// Check if we've finished the path
|
|
90
|
+
if (this.currentWaypoint >= this.waypoints.length) {
|
|
91
|
+
if (this.loop) {
|
|
92
|
+
// Loop back to beginning
|
|
93
|
+
this.currentWaypoint = 0;
|
|
94
|
+
} else {
|
|
95
|
+
// End path following
|
|
96
|
+
this.finished = true;
|
|
97
|
+
Matter.Body.setVelocity(body, { x: 0, y: 0 });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Pause at waypoint if specified
|
|
103
|
+
if (this.pauseAtWaypoints > 0) {
|
|
104
|
+
this.isPaused = true;
|
|
105
|
+
this.pauseTimer = 0;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Calculate movement direction toward current waypoint
|
|
111
|
+
if (distance > 0) {
|
|
112
|
+
this.direction = {
|
|
113
|
+
x: distanceX / distance,
|
|
114
|
+
y: distanceY / distance
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Apply velocity toward current waypoint
|
|
119
|
+
Matter.Body.setVelocity(body, {
|
|
120
|
+
x: this.direction.x * this.speed,
|
|
121
|
+
y: this.direction.y * this.speed
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if path has been fully traversed
|
|
127
|
+
*
|
|
128
|
+
* @returns True if path following is complete
|
|
129
|
+
*/
|
|
130
|
+
isFinished(): boolean {
|
|
131
|
+
return this.finished;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get current waypoint index
|
|
136
|
+
*
|
|
137
|
+
* @returns The index of the current target waypoint
|
|
138
|
+
*/
|
|
139
|
+
getCurrentWaypoint(): number {
|
|
140
|
+
return this.currentWaypoint;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Set a new path of waypoints
|
|
145
|
+
*
|
|
146
|
+
* @param waypoints - New waypoints to follow
|
|
147
|
+
* @param resetProgress - Whether to start from the first waypoint
|
|
148
|
+
*/
|
|
149
|
+
setWaypoints(waypoints: Array<{ x: number, y: number }>, resetProgress: boolean = true): void {
|
|
150
|
+
this.waypoints = waypoints;
|
|
151
|
+
if (resetProgress) {
|
|
152
|
+
this.currentWaypoint = 0;
|
|
153
|
+
}
|
|
154
|
+
this.finished = waypoints.length === 0;
|
|
155
|
+
}
|
|
156
|
+
}
|