@react-shimeji/core 0.3.3 → 0.3.4

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/index.cjs CHANGED
@@ -1647,6 +1647,9 @@ function environmentRectangle(bounds) {
1647
1647
  }
1648
1648
  var PLATFORM_NEARBY_DISTANCE = 400;
1649
1649
  var PLATFORM_EDGE_TOLERANCE = 2;
1650
+ var MASCOT_HITBOX_MAX_WIDTH = 32;
1651
+ var MASCOT_HITBOX_MAX_HEIGHT = 64;
1652
+ var MASCOT_COLLISION_LOOKAHEAD = 4;
1650
1653
  var IE_BEHAVIOR_NAME_PATTERN = /wall|climb|crawl|壁|登|よじ/i;
1651
1654
  function isIEBehavior(behavior) {
1652
1655
  return behavior.conditions.some((condition) => /activeIE/i.test(condition)) || behavior.name.includes("IE") || behavior.name.includes("\uFF29\uFF25") || IE_BEHAVIOR_NAME_PATTERN.test(behavior.name) || behavior.actionName !== void 0 && (behavior.actionName.includes("IE") || behavior.actionName.includes("\uFF29\uFF25") || IE_BEHAVIOR_NAME_PATTERN.test(behavior.actionName));
@@ -1713,7 +1716,7 @@ var Mascot = class {
1713
1716
  random;
1714
1717
  forceInitialFall;
1715
1718
  /** Advances behavior, animation, physics, and rendering by one clock tick. */
1716
- tick(deltaMs, bounds, platforms = []) {
1719
+ tick(deltaMs, bounds, platforms = [], siblings = []) {
1717
1720
  if (this.destroyed) return;
1718
1721
  this.platforms = platforms;
1719
1722
  try {
@@ -1721,7 +1724,7 @@ var Mascot = class {
1721
1724
  this.accumulatedMs += Math.max(0, deltaMs);
1722
1725
  while (this.accumulatedMs >= this.frameDuration && !this.destroyed) {
1723
1726
  this.accumulatedMs -= this.frameDuration;
1724
- this.legacyTick(bounds, platforms);
1727
+ this.legacyTick(bounds, platforms, siblings);
1725
1728
  }
1726
1729
  } catch (error) {
1727
1730
  this.callbacks.error(error instanceof Error ? error : new Error(String(error)));
@@ -1734,6 +1737,24 @@ var Mascot = class {
1734
1737
  snapshot() {
1735
1738
  return { ...this.state };
1736
1739
  }
1740
+ /** Returns the mascot's compact, feet-aligned behavioral collision box. */
1741
+ collisionBox() {
1742
+ const sprite = this.spec.sprites[this.state.sprite];
1743
+ const spriteWidth = typeof sprite === "object" && sprite.width !== void 0 ? sprite.width : 128;
1744
+ const spriteHeight = typeof sprite === "object" && sprite.height !== void 0 ? sprite.height : 128;
1745
+ const width = Math.min(spriteWidth, MASCOT_HITBOX_MAX_WIDTH);
1746
+ const height = Math.min(spriteHeight, MASCOT_HITBOX_MAX_HEIGHT);
1747
+ const anchorX = this.state.lookRight ? spriteWidth - this.state.anchorX : this.state.anchorX;
1748
+ const visualLeft = this.state.x - anchorX;
1749
+ const visualTop = this.state.y - this.state.anchorY;
1750
+ return {
1751
+ id: this.id,
1752
+ x: visualLeft + (spriteWidth - width) / 2,
1753
+ y: visualTop + spriteHeight - height,
1754
+ width,
1755
+ height
1756
+ };
1757
+ }
1737
1758
  /** Removes listeners, DOM nodes, and object URLs owned by this mascot. */
1738
1759
  destroy() {
1739
1760
  if (this.destroyed) return;
@@ -1762,11 +1783,13 @@ var Mascot = class {
1762
1783
  if (!this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms)) return;
1763
1784
  }
1764
1785
  }
1765
- legacyTick(bounds, platforms) {
1786
+ legacyTick(bounds, platforms, siblings) {
1766
1787
  this.ensureBehavior(bounds, platforms, false);
1767
1788
  if (!this.currentBehavior) return;
1789
+ const previous = { x: this.state.x, y: this.state.y };
1768
1790
  const result = this.actions.step(this.createEnvironment(bounds, platforms), bounds, platforms);
1769
1791
  if (this.destroyed) return;
1792
+ this.avoidSiblingCollision(previous, bounds, platforms, siblings);
1770
1793
  if (result === "lost-ground") {
1771
1794
  this.state.dragging = false;
1772
1795
  this.actions.cancel();
@@ -1784,6 +1807,31 @@ var Mascot = class {
1784
1807
  if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms);
1785
1808
  }
1786
1809
  }
1810
+ avoidSiblingCollision(previous, bounds, platforms, siblings) {
1811
+ const dx = this.state.x - previous.x;
1812
+ if (this.state.dragging || dx === 0 || this.state.y !== previous.y) return;
1813
+ const onHorizontalSurface = isOnFloor(previous, bounds, platforms) || isOnTop(previous, bounds) || platforms.some((platform) => isOnBottom(previous, platform));
1814
+ if (!onHorizontalSurface) return;
1815
+ const currentBox = this.collisionBox();
1816
+ const offsetX = currentBox.x - this.state.x;
1817
+ const previousBox = { ...currentBox, x: previous.x + offsetX };
1818
+ const movingRight = dx > 0;
1819
+ const sweptLeft = Math.min(previousBox.x, currentBox.x) - (movingRight ? 0 : MASCOT_COLLISION_LOOKAHEAD);
1820
+ const sweptRight = Math.max(previousBox.x + previousBox.width, currentBox.x + currentBox.width) + (movingRight ? MASCOT_COLLISION_LOOKAHEAD : 0);
1821
+ const previousCenterX = previousBox.x + previousBox.width / 2;
1822
+ const collision = siblings.some((sibling) => {
1823
+ if (sibling.id === this.id) return false;
1824
+ const siblingCenterX = sibling.x + sibling.width / 2;
1825
+ const isAhead = movingRight ? siblingCenterX >= previousCenterX : siblingCenterX <= previousCenterX;
1826
+ const overlapsVertically = currentBox.y < sibling.y + sibling.height && sibling.y < currentBox.y + currentBox.height;
1827
+ const crossesHorizontally = sweptLeft <= sibling.x + sibling.width && sibling.x <= sweptRight;
1828
+ return isAhead && overlapsVertically && crossesHorizontally;
1829
+ });
1830
+ if (!collision) return;
1831
+ this.state.x = previous.x;
1832
+ this.state.vx = 0;
1833
+ this.state.lookRight = !this.state.lookRight;
1834
+ }
1787
1835
  isOutsideVisibleBounds(bounds) {
1788
1836
  const sprite = this.spec.sprites[this.state.sprite];
1789
1837
  const width = typeof sprite === "object" && "width" in sprite && sprite.width !== void 0 ? sprite.width : 128;
@@ -2205,7 +2253,9 @@ var ShimejiEngine = class {
2205
2253
  this.lastFrameTime = timestamp;
2206
2254
  const delta = Math.max(0, Math.min(rawDelta, this.options.maxDeltaTime));
2207
2255
  const { bounds, platforms } = this.readFrameGeometry();
2208
- for (const mascot of [...this.mascots.values()]) mascot.tick(delta, bounds, platforms);
2256
+ const mascots = [...this.mascots.values()];
2257
+ const collisionBoxes = mascots.map((mascot) => mascot.collisionBox());
2258
+ for (const mascot of mascots) mascot.tick(delta, bounds, platforms, collisionBoxes);
2209
2259
  this.emitState();
2210
2260
  this.animationFrame = this.container.ownerDocument.defaultView?.requestAnimationFrame(this.onAnimationFrame);
2211
2261
  };