core2d 2.10.4 → 2.11.2

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/Sprite.mjs CHANGED
@@ -6,363 +6,695 @@ import { Frame } from "./Frame.mjs";
6
6
  import { Rect } from "./Rect.mjs";
7
7
  import { Static } from "./Static.mjs";
8
8
 
9
+ /**
10
+ * Represents a sprite, which is a basic game object that can be rendered on the screen.
11
+ * @extends Rect
12
+ */
9
13
  export class Sprite extends Rect {
10
- constructor() {
11
- super();
12
- this.accelerationX = 0;
13
- this.accelerationY = 0;
14
- this.alpha = 1;
15
- this.boundary = null;
16
- this.color = null;
17
- this.essential = false;
18
- this.expiration = 0;
19
- this.expired = false;
20
- this.layerIndex = 0;
21
- this.maxSpeedX = 0;
22
- this.maxSpeedY = 0;
23
- this.solid = false;
24
- this.speedX = 0;
25
- this.speedY = 0;
26
- this.visible = true;
27
- this._animation = null;
28
- this._lastSpeedX = 0;
29
- this._lastSpeedY = 0;
30
- this._lastX = this.x;
31
- this._lastY = this.y;
32
- this._tags = {};
33
- this._tick = 0;
34
- }
35
-
36
- get angle() {
37
- return Static.toDegrees(Math.atan2(this.speedY, this.speedX));
38
- }
39
-
40
- get direction() {
41
- const DIRECTION = new Direction();
42
-
43
- if (this.x < this._lastX) {
44
- DIRECTION.setLeft();
45
- } else if (this.x > this._lastX) {
46
- DIRECTION.setRight();
47
- }
48
-
49
- if (this.y < this._lastY) {
50
- DIRECTION.setTop();
51
- } else if (this.y > this._lastY) {
52
- DIRECTION.setBottom();
53
- }
54
-
55
- return DIRECTION;
56
- }
57
-
58
- get image() {
59
- return this._animation && this._animation.image;
60
- }
61
-
62
- get tick() {
63
- return this._tick;
64
- }
65
-
66
- setAccelerationX(accelerationX = 0) {
67
- this.accelerationX = accelerationX;
68
- return this;
69
- }
70
-
71
- setAccelerationY(accelerationY = 0) {
72
- this.accelerationY = accelerationY;
73
- return this;
74
- }
75
-
76
- setAlpha(alpha = 1) {
77
- this.alpha = alpha;
78
- return this;
79
- }
80
-
81
- setBoundary(rect = null) {
82
- this.boundary = rect || this.scene;
83
- return this;
84
- }
85
-
86
- setColor(color) {
87
- this.color = color;
88
- return this;
89
- }
90
-
91
- setEssential(isEssential = true) {
92
- this.essential = isEssential;
93
- return this;
94
- }
95
-
96
- setExpiration(expiration = 0) {
97
- this.expiration = expiration;
98
- return this;
99
- }
100
-
101
- setExpired(isExpired = true) {
102
- this.expired = isExpired;
103
- return this;
104
- }
105
-
106
- setLayerIndex(layerIndex = 0) {
107
- this.layerIndex = layerIndex;
108
- return this;
109
- }
110
-
111
- setMaxSpeedX(maxSpeedX = 0) {
112
- this.maxSpeedX = maxSpeedX;
113
- return this;
114
- }
115
-
116
- setMaxSpeedY(maxSpeedY = 0) {
117
- this.maxSpeedY = maxSpeedY;
118
- return this;
119
- }
120
-
121
- setSolid(isSolid = true) {
122
- this.solid = isSolid;
123
- return this;
124
- }
125
-
126
- setSpeedX(speedX = 0) {
127
- this.speedX = speedX;
128
- return this;
129
- }
130
-
131
- setSpeedY(speedY = 0) {
132
- this.speedY = speedY;
133
- return this;
134
- }
135
-
136
- setVisible(isVisible = true) {
137
- this.visible = isVisible;
138
- return this;
139
- }
140
-
141
- setAnimation(animation) {
142
- if (animation == this._animation) {
143
- return this;
144
- }
145
-
146
- this._animation = animation;
147
- this._animation.setFrameIndex(0);
148
- this.height = this._animation.height;
149
- this.width = this._animation.width;
150
- return this;
151
- }
152
-
153
- setImage(image) {
154
- this.setAnimation(new Animation([new Frame(image)]));
155
- return this;
156
- }
157
-
158
- set image(image) {
159
- this.setImage(image);
160
- }
161
-
162
- setSpeedToAngle(speed, degrees) {
163
- const RADIANS = Static.toRadians(degrees);
164
- this.setSpeedX(speed * Math.cos(RADIANS));
165
- this.setSpeedY(speed * Math.sin(RADIANS));
166
- return this;
167
- }
168
-
169
- setSpeedToPoint(speed, point) {
170
- const SQUARE_DISTANCE = Math.abs(this.centerX - point.x) + Math.abs(this.centerY - point.y);
171
- this.setSpeedX((point.x - this.centerX) * speed / SQUARE_DISTANCE);
172
- this.setSpeedY((point.y - this.centerY) * speed / SQUARE_DISTANCE);
173
- return this;
174
- }
175
-
176
- addTag(tag) {
177
- this._tags[tag] = true;
178
- return this;
179
- }
180
-
181
- bounceFrom(direction) {
182
- if ((this.speedX < 0 && direction.left) || (this.speedX > 0 && direction.right)) {
183
- this.bounceX();
184
- }
185
-
186
- if ((this.speedY < 0 && direction.top) || (this.speedY > 0 && direction.bottom)) {
187
- this.bounceY();
188
- }
189
-
190
- return this;
191
- }
192
-
193
- bounceX() {
194
- this.setSpeedX(this.speedX * -1);
195
- this.x += this.speedX;
196
- return this;
197
- }
198
-
199
- bounceY() {
200
- this.setSpeedY(this.speedY * -1);
201
- this.y += this.speedY;
202
- return this;
203
- }
204
-
205
- expire() {
206
- this.expired = true;
207
- }
208
-
209
- getCollision(sprite) {
210
- const DIRECTION = new Direction();
211
- const TA = this.top;
212
- const RA = this.right;
213
- const BA = this.bottom;
214
- const LA = this.left;
215
- const XA = this.centerX;
216
- const YA = this.centerY;
217
- const TB = sprite.top;
218
- const RB = sprite.right;
219
- const BB = sprite.bottom;
220
- const LB = sprite.left;
221
-
222
- if (XA <= LB && RA < RB) {
223
- DIRECTION.setRight();
224
- } else if (XA >= RB && LA > LB) {
225
- DIRECTION.setLeft();
226
- }
227
-
228
- if (YA <= TB && BA < BB) {
229
- DIRECTION.setBottom();
230
- } else if (YA >= BB && TA > TB) {
231
- DIRECTION.setTop();
232
- }
233
-
234
- return DIRECTION;
235
- }
236
-
237
- hasCollision(rect) {
238
- return !(
239
- this.left > rect.right ||
240
- this.right < rect.left ||
241
- this.top > rect.bottom ||
242
- this.bottom < rect.top
243
- );
244
- }
245
-
246
- hasTag(tag) {
247
- return this._tags[tag];
248
- }
249
-
250
- init() {
251
- // no default behavior
252
- }
253
-
254
- offBoundary() {
255
- this.setExpired();
256
- }
257
-
258
- onAnimationLoop() {
259
- // no default behavior
260
- }
261
-
262
- onCollision(sprite) {
263
- // no default behavior
264
- return sprite;
265
- }
266
-
267
- pause() {
268
- this._lastSpeedX = this.speedX;
269
- this._lastSpeedY = this.speedY;
270
- this.speedX = 0;
271
- this.speedY = 0;
272
- return this;
273
- }
274
-
275
- render(context) {
276
- if (!this.visible) {
277
- return false;
278
- }
279
-
280
- const X = Math.floor(this.x + this.scene.x);
281
- const Y = Math.floor(this.y + this.scene.y);
282
-
283
- if (this.alpha < 1) {
284
- context.globalAlpha = this.alpha;
285
- }
286
-
287
- if (this.color) {
288
- context.fillStyle = this.color;
289
- context.fillRect(X, Y, this.width, this.height);
290
- }
291
-
292
- if (this._animation) {
293
- context.drawImage(this._animation.image, X, Y, this.width, this.height);
294
- }
295
-
296
- if (this.alpha < 1) {
297
- context.globalAlpha = 1;
298
- }
299
-
300
- return true;
301
- }
302
-
303
- resume() {
304
- this.speedX = this._lastSpeedX;
305
- this.speedY = this._lastSpeedY;
306
- return this;
307
- }
308
-
309
- stop() {
310
- this._lastSpeedX = 0;
311
- this._lastSpeedY = 0;
312
- this.speedX = 0;
313
- this.speedY = 0;
314
- }
315
-
316
- sync() {
317
- this._lastX = this.x;
318
- this._lastY = this.y;
319
- this.update();
320
-
321
- if (++this._tick == this.expiration) {
322
- this.setExpired();
323
- }
324
-
325
- if (this.expired) {
326
- return true;
327
- }
328
-
329
- if (this._animation && this._animation.sync()) {
330
- this.onAnimationLoop();
331
- }
332
-
333
- this.x += this.speedX;
334
- this.y += this.speedY;
335
- this.speedX += this.accelerationX;
336
- this.speedY += this.accelerationY;
337
-
338
- if (this.maxSpeedX && Math.abs(this.speedX) > this.maxSpeedX) {
339
- const SIGNAL = this.speedX / Math.abs(this.speedX);
340
- this.speedX = this.maxSpeedX * SIGNAL;
341
- }
342
-
343
- if (this.maxSpeedY && Math.abs(this.speedY) > this.maxSpeedY) {
344
- const SIGNAL = this.speedY / Math.abs(this.speedY);
345
- this.speedY = this.maxSpeedY * SIGNAL;
346
- }
347
-
348
- if (this.boundary && !this.hasCollision(this.boundary)) {
349
- this.offBoundary();
350
- }
351
-
352
- return false;
353
- }
354
-
355
- update() {
356
- // no default behavior
357
- }
358
-
359
- zoom(ratio) {
360
- const widthChange = this.width * ratio;
361
- this.width += widthChange;
362
- this.x -= widthChange / 2;
363
- const heightChange = this.height * ratio;
364
- this.height += heightChange;
365
- this.y -= heightChange / 2;
366
- return this;
367
- }
14
+ /**
15
+ * Creates a new Sprite.
16
+ */
17
+ constructor() {
18
+ super();
19
+
20
+ /**
21
+ * The horizontal acceleration of the sprite.
22
+ * @type {number}
23
+ */
24
+ this.accelerationX = 0;
25
+
26
+ /**
27
+ * The vertical acceleration of the sprite.
28
+ * @type {number}
29
+ */
30
+ this.accelerationY = 0;
31
+
32
+ /**
33
+ * The alpha transparency of the sprite.
34
+ * @type {number}
35
+ */
36
+ this.alpha = 1;
37
+
38
+ /**
39
+ * The boundary of the sprite. If the sprite goes outside of this boundary, the `offBoundary` method is called.
40
+ * @type {Rect}
41
+ */
42
+ this.boundary = null;
43
+
44
+ /**
45
+ * The color of the sprite.
46
+ * @type {string}
47
+ */
48
+ this.color = null;
49
+
50
+ /**
51
+ * Whether the sprite is essential. If an essential sprite expires, the scene expires as well.
52
+ * @type {boolean}
53
+ */
54
+ this.essential = false;
55
+
56
+ /**
57
+ * The expiration time of the sprite, in ticks.
58
+ * @type {number}
59
+ */
60
+ this.expiration = 0;
61
+
62
+ /**
63
+ * Whether the sprite has expired.
64
+ * @type {boolean}
65
+ */
66
+ this.expired = false;
67
+
68
+ /**
69
+ * The layer index of the sprite. Sprites with a higher layer index are rendered on top of sprites with a lower layer index.
70
+ * @type {number}
71
+ */
72
+ this.layerIndex = 0;
73
+
74
+ /**
75
+ * The maximum horizontal speed of the sprite.
76
+ * @type {number}
77
+ */
78
+ this.maxSpeedX = 0;
79
+
80
+ /**
81
+ * The maximum vertical speed of the sprite.
82
+ * @type {number}
83
+ */
84
+ this.maxSpeedY = 0;
85
+
86
+ /**
87
+ * The scene that the sprite belongs to.
88
+ * @type {import("./Scene.mjs").Scene}
89
+ */
90
+ this.scene = null;
91
+
92
+ /**
93
+ * Whether the sprite is solid. Solid sprites can collide with other solid sprites.
94
+ * @type {boolean}
95
+ */
96
+ this.solid = false;
97
+
98
+ /**
99
+ * The horizontal speed of the sprite.
100
+ * @type {number}
101
+ */
102
+ this.speedX = 0;
103
+
104
+ /**
105
+ * The vertical speed of the sprite.
106
+ * @type {number}
107
+ */
108
+ this.speedY = 0;
109
+
110
+ /**
111
+ * Whether the sprite is visible.
112
+ * @type {boolean}
113
+ */
114
+ this.visible = true;
115
+
116
+ /**
117
+ * The animation of the sprite.
118
+ * @type {Animation}
119
+ * @private
120
+ */
121
+ this._animation = null;
122
+
123
+ /**
124
+ * The last horizontal speed of the sprite.
125
+ * @type {number}
126
+ * @private
127
+ */
128
+ this._lastSpeedX = 0;
129
+
130
+ /**
131
+ * The last vertical speed of the sprite.
132
+ * @type {number}
133
+ * @private
134
+ */
135
+ this._lastSpeedY = 0;
136
+
137
+ /**
138
+ * The last horizontal position of the sprite.
139
+ * @type {number}
140
+ * @private
141
+ */
142
+ this._lastX = this.x;
143
+
144
+ /**
145
+ * The last vertical position of the sprite.
146
+ * @type {number}
147
+ * @private
148
+ */
149
+ this._lastY = this.y;
150
+
151
+ /**
152
+ * The tags of the sprite.
153
+ * @type {Object<string, boolean>}
154
+ * @private
155
+ */
156
+ this._tags = {};
157
+
158
+ /**
159
+ * The number of ticks that have passed since the sprite was created.
160
+ * @type {number}
161
+ * @private
162
+ */
163
+ this._tick = 0;
164
+ }
165
+
166
+ /**
167
+ * The angle of the sprite in degrees.
168
+ * @type {number}
169
+ */
170
+ get angle() {
171
+ return Static.toDegrees(Math.atan2(this.speedY, this.speedX));
172
+ }
173
+
174
+ /**
175
+ * The direction of the sprite.
176
+ * @type {Direction}
177
+ */
178
+ get direction() {
179
+ const DIRECTION = new Direction();
180
+
181
+ if (this.x < this._lastX) {
182
+ DIRECTION.setLeft();
183
+ } else if (this.x > this._lastX) {
184
+ DIRECTION.setRight();
185
+ }
186
+
187
+ if (this.y < this._lastY) {
188
+ DIRECTION.setTop();
189
+ } else if (this.y > this._lastY) {
190
+ DIRECTION.setBottom();
191
+ }
192
+
193
+ return DIRECTION;
194
+ }
195
+
196
+ /**
197
+ * The image of the sprite.
198
+ * @type {HTMLImageElement|HTMLCanvasElement}
199
+ */
200
+ get image() {
201
+ return this._animation && this._animation.image;
202
+ }
203
+
204
+ /**
205
+ * The number of ticks that have passed since the sprite was created.
206
+ * @type {number}
207
+ */
208
+ get tick() {
209
+ return this._tick;
210
+ }
211
+
212
+ /**
213
+ * Sets the horizontal acceleration of the sprite.
214
+ * @param {number} accelerationX The horizontal acceleration.
215
+ * @returns {Sprite} This sprite.
216
+ */
217
+ setAccelerationX(accelerationX = 0) {
218
+ this.accelerationX = accelerationX;
219
+ return this;
220
+ }
221
+
222
+ /**
223
+ * Sets the vertical acceleration of the sprite.
224
+ * @param {number} accelerationY The vertical acceleration.
225
+ * @returns {Sprite} This sprite.
226
+ */
227
+ setAccelerationY(accelerationY = 0) {
228
+ this.accelerationY = accelerationY;
229
+ return this;
230
+ }
231
+
232
+ /**
233
+ * Sets the alpha transparency of the sprite.
234
+ * @param {number} alpha The alpha transparency.
235
+ * @returns {Sprite} This sprite.
236
+ */
237
+ setAlpha(alpha = 1) {
238
+ this.alpha = alpha;
239
+ return this;
240
+ }
241
+
242
+ /**
243
+ * Sets the boundary of the sprite.
244
+ * @param {Rect} rect The boundary.
245
+ * @returns {Sprite} This sprite.
246
+ */
247
+ setBoundary(rect = null) {
248
+ this.boundary = rect || this.scene;
249
+ return this;
250
+ }
251
+
252
+ /**
253
+ * Sets the color of the sprite.
254
+ * @param {string} color The color.
255
+ * @returns {Sprite} This sprite.
256
+ */
257
+ setColor(color) {
258
+ this.color = color;
259
+ return this;
260
+ }
261
+
262
+ /**
263
+ * Sets whether the sprite is essential.
264
+ * @param {boolean} isEssential Whether the sprite is essential.
265
+ * @returns {Sprite} This sprite.
266
+ */
267
+ setEssential(isEssential = true) {
268
+ this.essential = isEssential;
269
+ return this;
270
+ }
271
+
272
+ /**
273
+ * Sets the expiration time of the sprite.
274
+ * @param {number} expiration The expiration time in ticks.
275
+ * @returns {Sprite} This sprite.
276
+ */
277
+ setExpiration(expiration = 0) {
278
+ this.expiration = expiration;
279
+ return this;
280
+ }
281
+
282
+ /**
283
+ * Sets whether the sprite has expired.
284
+ * @param {boolean} isExpired Whether the sprite has expired.
285
+ * @returns {Sprite} This sprite.
286
+ */
287
+ setExpired(isExpired = true) {
288
+ this.expired = isExpired;
289
+ return this;
290
+ }
291
+
292
+ /**
293
+ * Sets the layer index of the sprite.
294
+ * @param {number} layerIndex The layer index.
295
+ * @returns {Sprite} This sprite.
296
+ */
297
+ setLayerIndex(layerIndex = 0) {
298
+ this.layerIndex = layerIndex;
299
+ return this;
300
+ }
301
+
302
+ /**
303
+ * Sets the maximum horizontal speed of the sprite.
304
+ * @param {number} maxSpeedX The maximum horizontal speed.
305
+ * @returns {Sprite} This sprite.
306
+ */
307
+ setMaxSpeedX(maxSpeedX = 0) {
308
+ this.maxSpeedX = maxSpeedX;
309
+ return this;
310
+ }
311
+
312
+ /**
313
+ * Sets the maximum vertical speed of the sprite.
314
+ * @param {number} maxSpeedY The maximum vertical speed.
315
+ * @returns {Sprite} This sprite.
316
+ */
317
+ setMaxSpeedY(maxSpeedY = 0) {
318
+ this.maxSpeedY = maxSpeedY;
319
+ return this;
320
+ }
321
+
322
+ /**
323
+ * Sets whether the sprite is solid.
324
+ * @param {boolean} isSolid Whether the sprite is solid.
325
+ * @returns {Sprite} This sprite.
326
+ */
327
+ setSolid(isSolid = true) {
328
+ this.solid = isSolid;
329
+ return this;
330
+ }
331
+
332
+ /**
333
+ * Sets the horizontal speed of the sprite.
334
+ * @param {number} speedX The horizontal speed.
335
+ * @returns {Sprite} This sprite.
336
+ */
337
+ setSpeedX(speedX = 0) {
338
+ this.speedX = speedX;
339
+ return this;
340
+ }
341
+
342
+ /**
343
+ * Sets the vertical speed of the sprite.
344
+ * @param {number} speedY The vertical speed.
345
+ * @returns {Sprite} This sprite.
346
+ */
347
+ setSpeedY(speedY = 0) {
348
+ this.speedY = speedY;
349
+ return this;
350
+ }
351
+
352
+ /**
353
+ * Sets whether the sprite is visible.
354
+ * @param {boolean} isVisible Whether the sprite is visible.
355
+ * @returns {Sprite} This sprite.
356
+ */
357
+ setVisible(isVisible = true) {
358
+ this.visible = isVisible;
359
+ return this;
360
+ }
361
+
362
+ /**
363
+ * Sets the animation of the sprite.
364
+ * @param {Animation} animation The animation.
365
+ * @returns {Sprite} This sprite.
366
+ */
367
+ setAnimation(animation) {
368
+ if (animation == this._animation) {
369
+ return this;
370
+ }
371
+
372
+ this._animation = animation;
373
+ this._animation.setFrameIndex(0);
374
+ this.height = this._animation.height;
375
+ this.width = this._animation.width;
376
+ return this;
377
+ }
378
+
379
+ /**
380
+ * Sets the image of the sprite.
381
+ * @param {HTMLImageElement|HTMLCanvasElement} image The image.
382
+ * @returns {Sprite} This sprite.
383
+ */
384
+ setImage(image) {
385
+ this.setAnimation(new Animation([new Frame(image)]));
386
+ return this;
387
+ }
388
+
389
+ /**
390
+ * The image of the sprite.
391
+ * @type {HTMLImageElement|HTMLCanvasElement}
392
+ */
393
+ set image(image) {
394
+ this.setImage(image);
395
+ }
396
+
397
+ /**
398
+ * Sets the speed of the sprite to an angle.
399
+ * @param {number} speed The speed.
400
+ * @param {number} degrees The angle in degrees.
401
+ * @returns {Sprite} This sprite.
402
+ */
403
+ setSpeedToAngle(speed, degrees) {
404
+ const RADIANS = Static.toRadians(degrees);
405
+ this.setSpeedX(speed * Math.cos(RADIANS));
406
+ this.setSpeedY(speed * Math.sin(RADIANS));
407
+ return this;
408
+ }
409
+
410
+ /**
411
+ * Sets the speed of the sprite to a point.
412
+ * @param {number} speed The speed.
413
+ * @param {Point} point The point.
414
+ * @returns {Sprite} This sprite.
415
+ */
416
+ setSpeedToPoint(speed, point) {
417
+ const SQUARE_DISTANCE =
418
+ Math.abs(this.centerX - point.x) + Math.abs(this.centerY - point.y);
419
+ this.setSpeedX(((point.x - this.centerX) * speed) / SQUARE_DISTANCE);
420
+ this.setSpeedY(((point.y - this.centerY) * speed) / SQUARE_DISTANCE);
421
+ return this;
422
+ }
423
+
424
+ /**
425
+ * Adds a tag to the sprite.
426
+ * @param {string} tag The tag.
427
+ * @returns {Sprite} This sprite.
428
+ */
429
+ addTag(tag) {
430
+ this._tags[tag] = true;
431
+ return this;
432
+ }
433
+
434
+ /**
435
+ * Bounces the sprite from a direction.
436
+ * @param {Direction} direction The direction.
437
+ * @returns {Sprite} This sprite.
438
+ */
439
+ bounceFrom(direction) {
440
+ if (
441
+ (this.speedX < 0 && direction.left) ||
442
+ (this.speedX > 0 && direction.right)
443
+ ) {
444
+ this.bounceX();
445
+ }
446
+
447
+ if (
448
+ (this.speedY < 0 && direction.top) ||
449
+ (this.speedY > 0 && direction.bottom)
450
+ ) {
451
+ this.bounceY();
452
+ }
453
+
454
+ return this;
455
+ }
456
+
457
+ /**
458
+ * Bounces the sprite horizontally.
459
+ * @returns {Sprite} This sprite.
460
+ */
461
+ bounceX() {
462
+ this.setSpeedX(this.speedX * -1);
463
+ this.x += this.speedX;
464
+ return this;
465
+ }
466
+
467
+ /**
468
+ * Bounces the sprite vertically.
469
+ * @returns {Sprite} This sprite.
470
+ */
471
+ bounceY() {
472
+ this.setSpeedY(this.speedY * -1);
473
+ this.y += this.speedY;
474
+ return this;
475
+ }
476
+
477
+ /**
478
+ * Expires the sprite.
479
+ */
480
+ expire() {
481
+ this.expired = true;
482
+ }
483
+
484
+ /**
485
+ * Gets the collision direction with another sprite.
486
+ * @param {Sprite} sprite The other sprite.
487
+ * @returns {Direction} The collision direction.
488
+ */
489
+ getCollision(sprite) {
490
+ const DIRECTION = new Direction();
491
+ const TA = this.top;
492
+ const RA = this.right;
493
+ const BA = this.bottom;
494
+ const LA = this.left;
495
+ const XA = this.centerX;
496
+ const YA = this.centerY;
497
+ const TB = sprite.top;
498
+ const RB = sprite.right;
499
+ const BB = sprite.bottom;
500
+ const LB = sprite.left;
501
+
502
+ if (XA <= LB && RA < RB) {
503
+ DIRECTION.setRight();
504
+ } else if (XA >= RB && LA > LB) {
505
+ DIRECTION.setLeft();
506
+ }
507
+
508
+ if (YA <= TB && BA < BB) {
509
+ DIRECTION.setBottom();
510
+ } else if (YA >= BB && TA > TB) {
511
+ DIRECTION.setTop();
512
+ }
513
+
514
+ return DIRECTION;
515
+ }
516
+
517
+ /**
518
+ * Checks if the sprite has a collision with a rectangle.
519
+ * @param {Rect} rect The rectangle.
520
+ * @returns {boolean} Whether the sprite has a collision with the rectangle.
521
+ */
522
+ hasCollision(rect) {
523
+ return !(
524
+ this.left > rect.right ||
525
+ this.right < rect.left ||
526
+ this.top > rect.bottom ||
527
+ this.bottom < rect.top
528
+ );
529
+ }
530
+
531
+ /**
532
+ * Checks if the sprite has a tag.
533
+ * @param {string} tag The tag.
534
+ * @returns {boolean} Whether the sprite has the tag.
535
+ */
536
+ hasTag(tag) {
537
+ return this._tags[tag];
538
+ }
539
+
540
+ /**
541
+ * Initializes the sprite.
542
+ */
543
+ init() {
544
+ // no default behavior
545
+ }
546
+
547
+ /**
548
+ * Called when the sprite goes off its boundary.
549
+ */
550
+ offBoundary() {
551
+ this.setExpired();
552
+ }
553
+
554
+ /**
555
+ * Called when the animation of the sprite loops.
556
+ */
557
+ onAnimationLoop() {
558
+ // no default behavior
559
+ }
560
+
561
+ /**
562
+ * Called when the sprite collides with another sprite.
563
+ * @param {Sprite} sprite The other sprite.
564
+ * @returns {Sprite} The other sprite.
565
+ */
566
+ onCollision(sprite) {
567
+ // no default behavior
568
+ return sprite;
569
+ }
570
+
571
+ /**
572
+ * Pauses the sprite.
573
+ * @returns {Sprite} This sprite.
574
+ */
575
+ pause() {
576
+ this._lastSpeedX = this.speedX;
577
+ this._lastSpeedY = this.speedY;
578
+ this.speedX = 0;
579
+ this.speedY = 0;
580
+ return this;
581
+ }
582
+
583
+ /**
584
+ * Renders the sprite.
585
+ * @param {CanvasRenderingContext2D} context The rendering context.
586
+ * @returns {boolean} Whether the sprite was rendered.
587
+ */
588
+ render(context) {
589
+ if (!this.visible) {
590
+ return false;
591
+ }
592
+
593
+ const X = Math.floor(this.x + this.scene.x);
594
+ const Y = Math.floor(this.y + this.scene.y);
595
+
596
+ if (this.alpha < 1) {
597
+ context.globalAlpha = this.alpha;
598
+ }
599
+
600
+ if (this.color) {
601
+ context.fillStyle = this.color;
602
+ context.fillRect(X, Y, this.width, this.height);
603
+ }
604
+
605
+ if (this._animation) {
606
+ context.drawImage(this._animation.image, X, Y, this.width, this.height);
607
+ }
608
+
609
+ if (this.alpha < 1) {
610
+ context.globalAlpha = 1;
611
+ }
612
+
613
+ return true;
614
+ }
615
+
616
+ /**
617
+ * Resumes the sprite.
618
+ * @returns {Sprite} This sprite.
619
+ */
620
+ resume() {
621
+ this.speedX = this._lastSpeedX;
622
+ this.speedY = this._lastSpeedY;
623
+ return this;
624
+ }
625
+
626
+ /**
627
+ * Stops the sprite.
628
+ */
629
+ stop() {
630
+ this._lastSpeedX = 0;
631
+ this._lastSpeedY = 0;
632
+ this.speedX = 0;
633
+ this.speedY = 0;
634
+ }
635
+
636
+ /**
637
+ * Synchronizes the sprite.
638
+ * @returns {boolean} Whether the sprite has expired.
639
+ */
640
+ sync() {
641
+ this._lastX = this.x;
642
+ this._lastY = this.y;
643
+ this.update();
644
+
645
+ if (++this._tick == this.expiration) {
646
+ this.setExpired();
647
+ }
648
+
649
+ if (this.expired) {
650
+ return true;
651
+ }
652
+
653
+ if (this._animation && this._animation.sync()) {
654
+ this.onAnimationLoop();
655
+ }
656
+
657
+ this.x += this.speedX;
658
+ this.y += this.speedY;
659
+ this.speedX += this.accelerationX;
660
+ this.speedY += this.accelerationY;
661
+
662
+ if (this.maxSpeedX && Math.abs(this.speedX) > this.maxSpeedX) {
663
+ const SIGNAL = this.speedX / Math.abs(this.speedX);
664
+ this.speedX = this.maxSpeedX * SIGNAL;
665
+ }
666
+
667
+ if (this.maxSpeedY && Math.abs(this.speedY) > this.maxSpeedY) {
668
+ const SIGNAL = this.speedY / Math.abs(this.speedY);
669
+ this.speedY = this.maxSpeedY * SIGNAL;
670
+ }
671
+
672
+ if (this.boundary && !this.hasCollision(this.boundary)) {
673
+ this.offBoundary();
674
+ }
675
+
676
+ return false;
677
+ }
678
+
679
+ /**
680
+ * Updates the sprite.
681
+ */
682
+ update() {
683
+ // no default behavior
684
+ }
685
+
686
+ /**
687
+ * Zooms the sprite.
688
+ * @param {number} ratio The zoom ratio.
689
+ * @returns {Sprite} This sprite.
690
+ */
691
+ zoom(ratio) {
692
+ const widthChange = this.width * ratio;
693
+ this.width += widthChange;
694
+ this.x -= widthChange / 2;
695
+ const heightChange = this.height * ratio;
696
+ this.height += heightChange;
697
+ this.y -= heightChange / 2;
698
+ return this;
699
+ }
368
700
  }