@makefully/platypus 4.1.6 → 4.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makefully/platypus",
3
- "version": "4.1.6",
3
+ "version": "4.1.9",
4
4
  "description": "2D tile-based HTML5 game engine with a component-based entity model",
5
5
  "license": "MIT",
6
6
  "contributors": [
@@ -73,7 +73,7 @@
73
73
  "webpack-dev-server": "^5.2.2"
74
74
  },
75
75
  "peerDependencies": {
76
- "@esotericsoftware/spine-pixi-v8": "^4.2.111",
76
+ "@esotericsoftware/spine-pixi-v8": "^4.3.7",
77
77
  "@pixi/sound": "github:Makefully-Studios/sound#feature/media-file",
78
78
  "@tweenjs/tween.js": "^23.1.1",
79
79
  "pako": "^2.1.0",
@@ -376,9 +376,17 @@ export default createComponentClass(/** @lends platypus.components.RenderSpine.p
376
376
  spineAtlas = platypus.assetCache.get(this.atlas),
377
377
  spineJsonParser = new SkeletonJson(new AtlasAttachmentLoader(spineAtlas)),
378
378
  skeletonData = spineJsonParser.readSkeletonData(skeleton),
379
- spine = this.spine = new Spine(skeletonData),
379
+ spine = this.spine = new Spine({skeletonData, autoUpdate: false}),
380
380
  map = createAnimationMap(this.animationMap, skeleton.animations);
381
381
 
382
+ // Spine 4.3 inherits Pixi container world-transform deltas into skeleton physics by default.
383
+ // Platypus pans the camera by moving the world container, which shifts every entity's world
384
+ // transform even when owner.x/y are unchanged — causing unwanted physics on stationary characters.
385
+ // Use the `apply-spine-physics` event for intentional movement-driven physics instead.
386
+ spine.setPhysicsPositionInheritanceFactor(0, 0);
387
+ spine.physicsRotationInheritanceFactor = 0;
388
+ spine.resetPhysicsTransform();
389
+
382
390
  let animation = '';
383
391
 
384
392
  spine.state.addListener({
@@ -414,7 +422,6 @@ export default createComponentClass(/** @lends platypus.components.RenderSpine.p
414
422
  this.owner.triggerEvent('animation-ended', animationName);
415
423
  }
416
424
  });
417
- spine.autoUpdate = false;
418
425
 
419
426
  this.stateBased = map && this.stateBased;
420
427
  this.eventBased = map && this.eventBased;
@@ -621,7 +628,7 @@ export default createComponentClass(/** @lends platypus.components.RenderSpine.p
621
628
  for (x = 0; x < slots.length; x++) {
622
629
  slot = this.spine.skeleton.findSlot(slots[x]);
623
630
  if (slot) {
624
- slot.color.setFromString(color);
631
+ slot.pose.color.setFromString(color);
625
632
  }
626
633
  }
627
634
  },
@@ -660,7 +667,7 @@ export default createComponentClass(/** @lends platypus.components.RenderSpine.p
660
667
  * rotate: {x: 0, y: 0, degrees: 0}
661
668
  * }
662
669
  *
663
- * Has known vibration issues. Until pixi-spine 4.3, recommend 120 FPS on physics constraints on joints.
670
+ * Has known vibration issues at low frame rates. Spine 4.3 improves physics constraint stability.
664
671
  * Potential fixes: https://esotericsoftware.com/forum/d/28093-jittering-when-applying-physicstranslate-pixispine/5
665
672
  *
666
673
  * @event platypus.Entity#apply-spine-physics
@@ -672,7 +679,7 @@ export default createComponentClass(/** @lends platypus.components.RenderSpine.p
672
679
  }
673
680
 
674
681
  if (changes.rotate) {
675
- this.spine.skeleton.physicsTranslate(changes.rotate.x, changes.rotate.y, changes.rotate.degrees);
682
+ this.spine.skeleton.physicsRotate(changes.rotate.x, changes.rotate.y, changes.rotate.degrees);
676
683
  }
677
684
  }
678
685
  },
@@ -698,7 +705,7 @@ export default createComponentClass(/** @lends platypus.components.RenderSpine.p
698
705
  }
699
706
 
700
707
  this.spine.skeleton.setSkin(skin);
701
- this.spine.skeleton.setSlotsToSetupPose();
708
+ this.spine.skeleton.setupPoseSlots();
702
709
  },
703
710
  addToContainer: function () {
704
711
  this.owner.container.cullable = false;
@@ -257,7 +257,43 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
257
257
  * @type Number
258
258
  * @default 0
259
259
  */
260
- left: 0
260
+ left: 0,
261
+
262
+ /**
263
+ * Tiled parallax factor for horizontal scrolling. 1.0 scrolls with the camera; values less than 1.0 scroll slower (background); 0 is fixed to the screen.
264
+ *
265
+ * @property parallaxX
266
+ * @type number
267
+ * @default 1
268
+ */
269
+ parallaxX: 1,
270
+
271
+ /**
272
+ * Tiled parallax factor for vertical scrolling.
273
+ *
274
+ * @property parallaxY
275
+ * @type number
276
+ * @default 1
277
+ */
278
+ parallaxY: 1,
279
+
280
+ /**
281
+ * Map parallax origin X in world coordinates (Tiled `parallaxoriginx`, plus any map offset).
282
+ *
283
+ * @property parallaxOriginX
284
+ * @type number
285
+ * @default 0
286
+ */
287
+ parallaxOriginX: 0,
288
+
289
+ /**
290
+ * Map parallax origin Y in world coordinates (Tiled `parallaxoriginy`, plus any map offset).
291
+ *
292
+ * @property parallaxOriginY
293
+ * @type number
294
+ * @default 0
295
+ */
296
+ parallaxOriginY: 0
261
297
  },
262
298
 
263
299
  /**
@@ -401,14 +437,7 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
401
437
 
402
438
  if (map) {
403
439
  this.updateTile(tile, map[z || 0], x, y);
404
- // Only flag a re-render if the changed tile is within the currently
405
- // cached region. Changes outside the cache will be drawn correctly
406
- // the next time the camera pans to include them.
407
- if (!this.cache.empty &&
408
- x >= this.cache.left && x <= this.cache.right &&
409
- y >= this.cache.top && y <= this.cache.bottom) {
410
- this.updateCache = true;
411
- }
440
+ this.updateCache = true;
412
441
  }
413
442
  },
414
443
 
@@ -422,70 +451,13 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
422
451
  },
423
452
 
424
453
  /**
425
- * Forces an immediate full re-render centered on the current camera
426
- * position. Call this after bulk tile changes to ensure the display
427
- * is up to date without requiring a camera pan.
428
- *
429
- * For the panning cache, we invalidate cachePixels and re-run the full
430
- * camera-update recache logic using the last known camera, which correctly
431
- * handles texture swapping, tile population, and sprite positioning.
454
+ * Forces an immediate full re-render of all cached tile textures.
455
+ * Call this after bulk tile changes to ensure the display is up to date
456
+ * without requiring a camera pan.
432
457
  */
433
458
  "force-render": function () {
434
- if (this.cacheAll) {
435
- // Single/full cache — re-render entire layer.
436
- this.populateTiles(this.cache, null);
437
- this.renderCache(this.cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
438
- } else if (this.cacheTexture) {
439
- // Panning cache — invalidate cachePixels so the recache branch
440
- // runs, then execute the full camera-update recache inline.
441
- const
442
- {viewport: vp} = this.owner.parent.worldCamera,
443
- {cache, cachePixels} = this,
444
- tempC = tempCache,
445
- ctw = this.cacheTilesWidth - 1,
446
- cth = this.cacheTilesHeight - 1,
447
- ctw2 = ctw / 2,
448
- cth2 = cth / 2;
449
-
450
- this.convertCamera(vp);
451
-
452
- // Force the recache branch to run regardless of camera position.
453
- cachePixels.empty = true;
454
-
455
- tempC.setAll(
456
- Math.round((this.laxCam.x - this.left) / this.tileWidth - ctw2) + ctw2,
457
- Math.round((this.laxCam.y - this.top) / this.tileHeight - cth2) + cth2,
458
- ctw, cth
459
- );
460
- if (tempC.left < 0) {
461
- tempC.moveX(tempC.halfWidth);
462
- } else if (tempC.right > this.tilesWidth - 1) {
463
- tempC.moveX(this.tilesWidth - 1 - tempC.halfWidth);
464
- }
465
- if (tempC.top < 0) {
466
- tempC.moveY(tempC.halfHeight);
467
- } else if (tempC.bottom > this.tilesHeight - 1) {
468
- tempC.moveY(this.tilesHeight - 1 - tempC.halfHeight);
469
- }
470
-
471
- this.tilesSpriteCache.texture = this.cacheTexture;
472
- this.cacheTexture = this.cacheTexture.alternate;
473
- this.tilesSprite.texture = this.cacheTexture;
474
-
475
- this.populateTiles(tempC, cache);
476
- this.renderCache(tempC, this.cacheTexture, this.mapContainer, this.mapContainerWrapper, this.tilesSpriteCache);
477
- cache.set(tempC);
478
-
479
- cachePixels.setAll(
480
- (cache.x + 0.5) * this.tileWidth + this.left,
481
- (cache.y + 0.5) * this.tileHeight + this.top,
482
- (cache.width + 1) * this.tileWidth,
483
- (cache.height + 1) * this.tileHeight
484
- );
485
-
486
- this.tilesSprite.x = vp.left - this.laxCam.left + cache.left * this.tileWidth + this.left;
487
- this.tilesSprite.y = vp.top - this.laxCam.top + cache.top * this.tileHeight + this.top;
488
- }
459
+ this.refreshAllCaches();
460
+ this.updateCache = false;
489
461
  }
490
462
  },
491
463
 
@@ -535,8 +507,7 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
535
507
  'handle-render': function () {
536
508
  if (this.updateCache) {
537
509
  this.updateCache = false;
538
- this.populateTiles(this.cache, null);
539
- this.renderCache(this.cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
510
+ this.refreshAllCaches();
540
511
  }
541
512
  }
542
513
  });
@@ -596,21 +567,8 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
596
567
  },
597
568
  'handle-render': function () {
598
569
  if (this.updateCache) {
599
- const
600
- {cache, cacheGrid, cacheTilesHeight, cacheTilesWidth, tilesHeight, tilesWidth} = this,
601
- tsh = tilesHeight - 1,
602
- tsw = tilesWidth - 1;
603
-
604
570
  this.updateCache = false;
605
-
606
- for (let x = 0; x < cacheGrid.length; x++) {
607
- for (let y = 0; y < cacheGrid[x].length; y++) {
608
- cache.setBounds(x * cacheTilesWidth, y * cacheTilesHeight, Math.min((x + 1) * cacheTilesWidth, tsw), Math.min((y + 1) * cacheTilesHeight, tsh));
609
-
610
- this.populateTiles(cache, null);
611
- this.renderCacheWithExtrusion(cache, cacheGrid[x][y].texture, this.mapContainer, this.mapContainerWrapper);
612
- }
613
- }
571
+ this.refreshAllCaches();
614
572
  }
615
573
  }
616
574
  });
@@ -663,24 +621,7 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
663
621
  this.updateBufferRegion(laxCam);
664
622
  }
665
623
 
666
- const
667
- ctw = this.cacheTilesWidth - 1,
668
- cth = this.cacheTilesHeight - 1,
669
- ctw2 = ctw / 2,
670
- cth2 = cth / 2;
671
-
672
- //only attempt to draw children that are relevant
673
- tempC.setAll(Math.round((laxCam.x - this.left) / this.tileWidth - ctw2) + ctw2, Math.round((laxCam.y - this.top) / this.tileHeight - cth2) + cth2, ctw, cth);
674
- if (tempC.left < 0) {
675
- tempC.moveX(tempC.halfWidth);
676
- } else if (tempC.right > this.tilesWidth - 1) {
677
- tempC.moveX(this.tilesWidth - 1 - tempC.halfWidth);
678
- }
679
- if (tempC.top < 0) {
680
- tempC.moveY(tempC.halfHeight);
681
- } else if (tempC.bottom > this.tilesHeight - 1) {
682
- tempC.moveY(this.tilesHeight - 1 - tempC.halfHeight);
683
- }
624
+ this.alignCacheToCamera(tempC, vp);
684
625
 
685
626
  this.tilesSpriteCache.texture = this.cacheTexture;
686
627
  this.cacheTexture = this.cacheTexture.alternate;
@@ -698,17 +639,9 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
698
639
  this.tilesSprite.y = vp.top - laxCam.top + cache.top * this.tileHeight + this.top;
699
640
  },
700
641
  'handle-render': function () {
701
- if (this.updateCache && !this.cache.empty) {
642
+ if (this.updateCache) {
702
643
  this.updateCache = false;
703
- this.populateTiles(this.cache, null);
704
- this.renderCacheClear(this.cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
705
- this.tilesSpriteCache.texture = this.cacheTexture;
706
- this.cachePixels.setAll(
707
- (this.cache.x + 0.5) * this.tileWidth + this.left,
708
- (this.cache.y + 0.5) * this.tileHeight + this.top,
709
- (this.cache.width + 1) * this.tileWidth,
710
- (this.cache.height + 1) * this.tileHeight
711
- );
644
+ this.refreshAllCaches();
712
645
  }
713
646
  }
714
647
  });
@@ -746,24 +679,7 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
746
679
  this.updateBufferRegion(laxCam);
747
680
  }
748
681
 
749
- const
750
- ctw = this.cacheTilesWidth - 1,
751
- cth = this.cacheTilesHeight - 1,
752
- ctw2 = ctw / 2,
753
- cth2 = cth / 2;
754
-
755
- //only attempt to draw children that are relevant
756
- tempC.setAll(Math.round((laxCam.x - this.left) / this.tileWidth - ctw2) + ctw2, Math.round((laxCam.y - this.top) / this.tileHeight - cth2) + cth2, ctw, cth);
757
- if (tempC.left < 0) {
758
- tempC.moveX(tempC.halfWidth);
759
- } else if (tempC.right > this.tilesWidth - 1) {
760
- tempC.moveX(this.tilesWidth - 1 - tempC.halfWidth);
761
- }
762
- if (tempC.top < 0) {
763
- tempC.moveY(tempC.halfHeight);
764
- } else if (tempC.bottom > this.tilesHeight - 1) {
765
- tempC.moveY(this.tilesHeight - 1 - tempC.halfHeight);
766
- }
682
+ this.alignCacheToCamera(tempC, vp);
767
683
 
768
684
  this.populateTiles(tempC, null);
769
685
  cache.set(tempC);
@@ -873,6 +789,10 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
873
789
  * its world travel, laxCam is at the start of the layer's travel, and
874
790
  * likewise at the end.
875
791
  *
792
+ * Tiled `parallaxx` / `parallaxy` factors are then applied relative to the
793
+ * map's parallax origin so the layer's effective render position is:
794
+ * `layerPos - (camera - parallaxOrigin) * (1 - parallaxFactor)`.
795
+ *
876
796
  * IMPORTANT: laxCam is resized to the *actual viewport dimensions* (not
877
797
  * viewport + refreshBuffer). Its purpose is solely to compute the parallax
878
798
  * offset for sprite positioning (vp.left - laxCam.left). Inflating it with
@@ -885,7 +805,8 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
885
805
  worldPosX = worldWidth - camera.width,
886
806
  worldHeight = this.worldHeight / this.scaleY,
887
807
  worldPosY = worldHeight - camera.height,
888
- laxCam = this.laxCam;
808
+ laxCam = this.laxCam,
809
+ {parallaxOriginX, parallaxOriginY, parallaxX, parallaxY} = this;
889
810
 
890
811
  if ((worldWidth === this.layerWidth) || !worldPosX) {
891
812
  laxCam.moveX(camera.x);
@@ -899,6 +820,14 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
899
820
  laxCam.moveY((camera.top - this.top) * (this.layerHeight - camera.height) / worldPosY + camera.halfHeight + this.top);
900
821
  }
901
822
 
823
+ if (parallaxX !== 1) {
824
+ laxCam.moveX(laxCam.x + (camera.x - parallaxOriginX) * (1 - parallaxX));
825
+ }
826
+
827
+ if (parallaxY !== 1) {
828
+ laxCam.moveY(laxCam.y + (camera.y - parallaxOriginY) * (1 - parallaxY));
829
+ }
830
+
902
831
  // FIX: resize laxCam to the actual viewport size only — never camera + refreshBuffer.
903
832
  if (camera.width !== laxCam.width || camera.height !== laxCam.height) {
904
833
  laxCam.resize(camera.width, camera.height);
@@ -1030,7 +959,151 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
1030
959
  this.mapContainer.mask = new Graphics().rect(0, 0, this.cacheClipWidth, this.cacheClipHeight).fill(0x000000);
1031
960
  },
1032
961
 
962
+ /**
963
+ * Computes tile bounds for the panning cache centered on the current camera.
964
+ *
965
+ * @param {platypus.AABB} bounds - Receives the computed tile bounds.
966
+ * @param {platypus.AABB} viewport - The camera viewport.
967
+ */
968
+ alignCacheToCamera: function (bounds, viewport) {
969
+ const
970
+ ctw = this.cacheTilesWidth - 1,
971
+ cth = this.cacheTilesHeight - 1,
972
+ ctw2 = ctw / 2,
973
+ cth2 = cth / 2;
974
+
975
+ this.convertCamera(viewport);
976
+
977
+ bounds.setAll(
978
+ Math.round((this.laxCam.x - this.left) / this.tileWidth - ctw2) + ctw2,
979
+ Math.round((this.laxCam.y - this.top) / this.tileHeight - cth2) + cth2,
980
+ ctw, cth
981
+ );
982
+
983
+ if (bounds.left < 0) {
984
+ bounds.moveX(bounds.halfWidth);
985
+ } else if (bounds.right > this.tilesWidth - 1) {
986
+ bounds.moveX(this.tilesWidth - 1 - bounds.halfWidth);
987
+ }
988
+
989
+ if (bounds.top < 0) {
990
+ bounds.moveY(bounds.halfHeight);
991
+ } else if (bounds.bottom > this.tilesHeight - 1) {
992
+ bounds.moveY(this.tilesHeight - 1 - bounds.halfHeight);
993
+ }
994
+ },
995
+
996
+ /**
997
+ * Re-renders every texture in a multi-texture full-map cache grid.
998
+ */
999
+ refreshAllCacheGrid: function () {
1000
+ const
1001
+ {cache, cacheGrid, cacheTilesHeight, cacheTilesWidth, tilesHeight, tilesWidth} = this,
1002
+ tsh = tilesHeight - 1,
1003
+ tsw = tilesWidth - 1;
1004
+
1005
+ for (let x = 0; x < cacheGrid.length; x++) {
1006
+ for (let y = 0; y < cacheGrid[x].length; y++) {
1007
+ cache.setBounds(
1008
+ x * cacheTilesWidth,
1009
+ y * cacheTilesHeight,
1010
+ Math.min((x + 1) * cacheTilesWidth, tsw),
1011
+ Math.min((y + 1) * cacheTilesHeight, tsh)
1012
+ );
1013
+
1014
+ this.populate(cache, null);
1015
+ this.renderCacheWithExtrusion(cache, cacheGrid[x][y].texture, this.mapContainer, this.mapContainerWrapper);
1016
+ }
1017
+ }
1018
+ },
1019
+
1020
+ /**
1021
+ * Re-renders the current panning-cache window in place.
1022
+ * Uses the alternate render texture while drawing so we never render
1023
+ * into the texture that is currently displayed on screen.
1024
+ *
1025
+ * @param {platypus.AABB} [viewport] - Optional camera viewport.
1026
+ */
1027
+ refreshPanningCache: function (viewport) {
1028
+ const
1029
+ {cache, cachePixels, laxCam, left, top, tilesSprite} = this,
1030
+ vp = viewport ?? this.owner.parent.worldCamera?.viewport;
1031
+
1032
+ if (!vp) {
1033
+ return;
1034
+ }
1035
+
1036
+ if (cache.empty) {
1037
+ this.alignCacheToCamera(cache, vp);
1038
+ }
1039
+
1040
+ if (cache.empty) {
1041
+ return;
1042
+ }
1043
+
1044
+ this.detachCacheCopy();
1045
+
1046
+ this.tilesSpriteCache.texture = this.cacheTexture;
1047
+ this.cacheTexture = this.cacheTexture.alternate;
1048
+ tilesSprite.texture = this.cacheTexture;
1049
+
1050
+ this.populate(cache, null);
1051
+ this.renderCacheClear(cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
1052
+
1053
+ this.tilesSpriteCache.texture = this.cacheTexture;
1054
+ cachePixels.setAll(
1055
+ (cache.x + 0.5) * this.tileWidth + left,
1056
+ (cache.y + 0.5) * this.tileHeight + top,
1057
+ (cache.width + 1) * this.tileWidth,
1058
+ (cache.height + 1) * this.tileHeight
1059
+ );
1060
+
1061
+ this.convertCamera(vp);
1062
+ tilesSprite.x = vp.left - laxCam.left + cache.left * this.tileWidth + left;
1063
+ tilesSprite.y = vp.top - laxCam.top + cache.top * this.tileHeight + top;
1064
+ },
1065
+
1066
+ detachCacheCopy: function () {
1067
+ const
1068
+ {mapContainer, tilesSpriteCache} = this;
1069
+
1070
+ if (tilesSpriteCache?.parent === mapContainer) {
1071
+ mapContainer.removeChild(tilesSpriteCache);
1072
+ }
1073
+ },
1074
+
1075
+ /**
1076
+ * Re-renders all cached tile textures for the active caching strategy.
1077
+ *
1078
+ * @param {platypus.AABB} [viewport] - Optional camera viewport for panning caches.
1079
+ */
1080
+ refreshAllCaches: function (viewport) {
1081
+ if (!this.tileCache) {
1082
+ this.cachePixels.empty = true;
1083
+ return;
1084
+ }
1085
+
1086
+ if (this.cacheGrid) {
1087
+ this.refreshAllCacheGrid();
1088
+ return;
1089
+ }
1090
+
1091
+ if (this.cacheAll && this.cacheTexture) {
1092
+ this.populate(this.cache, null);
1093
+ this.render(this.cache, this.cacheTexture, this.mapContainer, this.mapContainerWrapper);
1094
+ return;
1095
+ }
1096
+
1097
+ if (this.cacheTexture) {
1098
+ this.refreshPanningCache(viewport);
1099
+ }
1100
+ },
1101
+
1033
1102
  populateTiles: function (bounds, oldBounds) {
1103
+ // When oldBounds is provided, only tiles outside the previous window are
1104
+ // drawn. That is correct for camera scrolling (reuse pixels from the old
1105
+ // cache texture) but must not be used for in-place tile edits — pass null
1106
+ // to redraw the entire bounds.
1034
1107
  const
1035
1108
  tiles = arrayCache.setUp();
1036
1109
 
@@ -1145,6 +1218,11 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
1145
1218
  container: wrapper,
1146
1219
  target: renderTexture
1147
1220
  });
1221
+
1222
+ if (oldCache?.parent === src) {
1223
+ src.removeChild(oldCache);
1224
+ }
1225
+
1148
1226
  renderTexture.requiresUpdate = true;
1149
1227
  },
1150
1228
 
@@ -1152,6 +1230,8 @@ export default createComponentClass(/** @lends platypus.components.RenderTiles.p
1152
1230
  const
1153
1231
  {renderer} = this;
1154
1232
 
1233
+ this.detachCacheCopy();
1234
+
1155
1235
  src.x = -bounds.left * this.tileWidth;
1156
1236
  src.y = -bounds.top * this.tileHeight;
1157
1237
  renderer.render({
@@ -699,7 +699,7 @@ export default createComponentClass(/** @lends platypus.components.TiledLoader.p
699
699
  },
700
700
 
701
701
  methods: {
702
- createLayer: function (entityKind, rawLayer, offsetX, offsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, combineRenderLayer, progress, entityLinker) {
702
+ createLayer: function (entityKind, rawLayer, offsetX, offsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, combineRenderLayer, progress, entityLinker, parallaxOriginX, parallaxOriginY) {
703
703
  const
704
704
  //This builds in parallaxing support by allowing the addition of width and height properties into Tiled layers so they pan at a separate rate than other layers.
705
705
  checkParallax = ({data, height, properties, width}) => {
@@ -730,6 +730,8 @@ export default createComponentClass(/** @lends platypus.components.TiledLoader.p
730
730
  },
731
731
 
732
732
  layer = decodeLayer(rawLayer),
733
+ parallaxX = layer.parallaxx ?? 1,
734
+ parallaxY = layer.parallaxy ?? 1,
733
735
  {
734
736
  offsetx: layerOffsetX = 0,
735
737
  offsety: layerOffsetY = 0,
@@ -835,6 +837,10 @@ export default createComponentClass(/** @lends platypus.components.TiledLoader.p
835
837
  layerDefinitionProperties.left = layerDefinitionProperties.x || mapOffsetX;
836
838
  layerDefinitionProperties.top = layerDefinitionProperties.y || mapOffsetY;
837
839
  layerDefinitionProperties.z = layerDefinitionProperties.z || this.layerZ;
840
+ layerDefinitionProperties.parallaxX = parallaxX;
841
+ layerDefinitionProperties.parallaxY = parallaxY;
842
+ layerDefinitionProperties.parallaxOriginX = parallaxOriginX;
843
+ layerDefinitionProperties.parallaxOriginY = parallaxOriginY;
838
844
 
839
845
  if (tilesets.length) {
840
846
  let imageIndex = 0;
@@ -910,7 +916,7 @@ export default createComponentClass(/** @lends platypus.components.TiledLoader.p
910
916
  }
911
917
  }
912
918
 
913
- if ((!this.separateTiles) && (combineRenderLayer?.tileHeight === tHeight) && (combineRenderLayer?.tileWidth === tWidth) && (combineRenderLayer?.columns === width) && (combineRenderLayer?.rows === height)) {
919
+ if ((!this.separateTiles) && (combineRenderLayer?.parallaxX ?? 1) === parallaxX && (combineRenderLayer?.parallaxY ?? 1) === parallaxY && (combineRenderLayer?.tileHeight === tHeight) && (combineRenderLayer?.tileWidth === tWidth) && (combineRenderLayer?.columns === width) && (combineRenderLayer?.rows === height)) {
914
920
  combineRenderLayer.triggerEvent('add-tiles', renderTiles);
915
921
  combineRenderLayer.triggerEvent('add-collision-tiles', collisionTiles);
916
922
 
@@ -998,7 +1004,9 @@ export default createComponentClass(/** @lends platypus.components.TiledLoader.p
998
1004
  height = level.height * tileHeight,
999
1005
  width = level.width * tileWidth,
1000
1006
  x = offsetMap ? getPowerOfTen(width) : 0,
1001
- y = offsetMap ? getPowerOfTen(height) : 0;
1007
+ y = offsetMap ? getPowerOfTen(height) : 0,
1008
+ parallaxOriginX = (level.parallaxoriginx ?? 0) + x,
1009
+ parallaxOriginY = (level.parallaxoriginy ?? 0) + y;
1002
1010
  let layer = null;
1003
1011
 
1004
1012
  createTilesetObjectGroupReference(tileSetTileData, tilesets);
@@ -1130,7 +1138,7 @@ export default createComponentClass(/** @lends platypus.components.TiledLoader.p
1130
1138
  switch (layerDefinition.type) {
1131
1139
  case 'imagelayer':
1132
1140
  layer = this.convertImageLayer(layerDefinition);
1133
- layer = this.createLayer(getProperty(layer.properties, 'entity') || 'image-layer', layer, x, y, layer.tilewidth, layer.tileheight, [layer.tileset], null, images, layer, progress, entityLinker);
1141
+ layer = this.createLayer(getProperty(layer.properties, 'entity') || 'image-layer', layer, x, y, layer.tilewidth, layer.tileheight, [layer.tileset], null, images, layer, progress, entityLinker, parallaxOriginX, parallaxOriginY);
1134
1142
  break;
1135
1143
  case 'objectgroup':
1136
1144
  this.setUpEntities(layerDefinition.objects.map((object) => getObjectCentered(object)), layerDefinition, x, y, tilesets, progress, entityLinker);
@@ -1138,7 +1146,7 @@ export default createComponentClass(/** @lends platypus.components.TiledLoader.p
1138
1146
  this.updateLoadingProgress(progress);
1139
1147
  break;
1140
1148
  case 'tilelayer':
1141
- layer = this.setupLayer(layerDefinition, layer, x, y, tileWidth, tileHeight, tilesets, tileSetTileData, images, progress, entityLinker);
1149
+ layer = this.setupLayer(layerDefinition, layer, x, y, tileWidth, tileHeight, tilesets, tileSetTileData, images, progress, entityLinker, parallaxOriginX, parallaxOriginY);
1142
1150
  break;
1143
1151
  default:
1144
1152
  platypus.debug.warn('Component TiledLoader: Platypus does not support Tiled layers of type "' + layerDefinition.type + '". This layer will not be loaded.');
@@ -1374,7 +1382,7 @@ export default createComponentClass(/** @lends platypus.components.TiledLoader.p
1374
1382
  };
1375
1383
  }()),
1376
1384
 
1377
- setupLayer: function (layer, combineRenderLayer, mapOffsetX, mapOffsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, progress, entityLinker) {
1385
+ setupLayer: function (layer, combineRenderLayer, mapOffsetX, mapOffsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, progress, entityLinker, parallaxOriginX, parallaxOriginY) {
1378
1386
  const
1379
1387
  entity = getProperty(layer.properties, 'entity') ?? 'render-layer', // default
1380
1388
  entityDefinition = platypus.game.settings.entities[entity] ?? standardEntityLayers[entity];
@@ -1393,9 +1401,9 @@ export default createComponentClass(/** @lends platypus.components.TiledLoader.p
1393
1401
  }
1394
1402
 
1395
1403
  if (canCombine) {
1396
- return this.createLayer(entity, layer, mapOffsetX, mapOffsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, combineRenderLayer, progress, entityLinker);
1404
+ return this.createLayer(entity, layer, mapOffsetX, mapOffsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, combineRenderLayer, progress, entityLinker, parallaxOriginX, parallaxOriginY);
1397
1405
  } else {
1398
- this.createLayer(entity, layer, mapOffsetX, mapOffsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, combineRenderLayer, progress, entityLinker);
1406
+ this.createLayer(entity, layer, mapOffsetX, mapOffsetY, tileWidth, tileHeight, tilesets, tileSetTileData, images, combineRenderLayer, progress, entityLinker, parallaxOriginX, parallaxOriginY);
1399
1407
  return null;
1400
1408
  }
1401
1409
  },