@react-shimeji/core 0.3.3 → 0.3.5

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.js CHANGED
@@ -1600,6 +1600,9 @@ function environmentRectangle(bounds) {
1600
1600
  }
1601
1601
  var PLATFORM_NEARBY_DISTANCE = 400;
1602
1602
  var PLATFORM_EDGE_TOLERANCE = 2;
1603
+ var MASCOT_HITBOX_MAX_WIDTH = 32;
1604
+ var MASCOT_HITBOX_MAX_HEIGHT = 64;
1605
+ var MASCOT_COLLISION_LOOKAHEAD = 4;
1603
1606
  var IE_BEHAVIOR_NAME_PATTERN = /wall|climb|crawl|壁|登|よじ/i;
1604
1607
  function isIEBehavior(behavior) {
1605
1608
  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));
@@ -1666,7 +1669,7 @@ var Mascot = class {
1666
1669
  random;
1667
1670
  forceInitialFall;
1668
1671
  /** Advances behavior, animation, physics, and rendering by one clock tick. */
1669
- tick(deltaMs, bounds, platforms = []) {
1672
+ tick(deltaMs, bounds, platforms = [], siblings = []) {
1670
1673
  if (this.destroyed) return;
1671
1674
  this.platforms = platforms;
1672
1675
  try {
@@ -1674,7 +1677,7 @@ var Mascot = class {
1674
1677
  this.accumulatedMs += Math.max(0, deltaMs);
1675
1678
  while (this.accumulatedMs >= this.frameDuration && !this.destroyed) {
1676
1679
  this.accumulatedMs -= this.frameDuration;
1677
- this.legacyTick(bounds, platforms);
1680
+ this.legacyTick(bounds, platforms, siblings);
1678
1681
  }
1679
1682
  } catch (error) {
1680
1683
  this.callbacks.error(error instanceof Error ? error : new Error(String(error)));
@@ -1687,6 +1690,24 @@ var Mascot = class {
1687
1690
  snapshot() {
1688
1691
  return { ...this.state };
1689
1692
  }
1693
+ /** Returns the mascot's compact, feet-aligned behavioral collision box. */
1694
+ collisionBox() {
1695
+ const sprite = this.spec.sprites[this.state.sprite];
1696
+ const spriteWidth = typeof sprite === "object" && sprite.width !== void 0 ? sprite.width : 128;
1697
+ const spriteHeight = typeof sprite === "object" && sprite.height !== void 0 ? sprite.height : 128;
1698
+ const width = Math.min(spriteWidth, MASCOT_HITBOX_MAX_WIDTH);
1699
+ const height = Math.min(spriteHeight, MASCOT_HITBOX_MAX_HEIGHT);
1700
+ const anchorX = this.state.lookRight ? spriteWidth - this.state.anchorX : this.state.anchorX;
1701
+ const visualLeft = this.state.x - anchorX;
1702
+ const visualTop = this.state.y - this.state.anchorY;
1703
+ return {
1704
+ id: this.id,
1705
+ x: visualLeft + (spriteWidth - width) / 2,
1706
+ y: visualTop + spriteHeight - height,
1707
+ width,
1708
+ height
1709
+ };
1710
+ }
1690
1711
  /** Removes listeners, DOM nodes, and object URLs owned by this mascot. */
1691
1712
  destroy() {
1692
1713
  if (this.destroyed) return;
@@ -1715,11 +1736,13 @@ var Mascot = class {
1715
1736
  if (!this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms)) return;
1716
1737
  }
1717
1738
  }
1718
- legacyTick(bounds, platforms) {
1739
+ legacyTick(bounds, platforms, siblings) {
1719
1740
  this.ensureBehavior(bounds, platforms, false);
1720
1741
  if (!this.currentBehavior) return;
1742
+ const previous = { x: this.state.x, y: this.state.y };
1721
1743
  const result = this.actions.step(this.createEnvironment(bounds, platforms), bounds, platforms);
1722
1744
  if (this.destroyed) return;
1745
+ this.avoidSiblingCollision(previous, bounds, platforms, siblings);
1723
1746
  if (result === "lost-ground") {
1724
1747
  this.state.dragging = false;
1725
1748
  this.actions.cancel();
@@ -1737,6 +1760,32 @@ var Mascot = class {
1737
1760
  if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms);
1738
1761
  }
1739
1762
  }
1763
+ avoidSiblingCollision(previous, bounds, platforms, siblings) {
1764
+ const dx = this.state.x - previous.x;
1765
+ if (this.state.dragging || dx === 0 || this.state.y !== previous.y) return;
1766
+ const onHorizontalSurface = isOnFloor(previous, bounds, platforms) || isOnTop(previous, bounds) || platforms.some((platform) => isOnBottom(previous, platform));
1767
+ if (!onHorizontalSurface) return;
1768
+ const currentBox = this.collisionBox();
1769
+ const offsetX = currentBox.x - this.state.x;
1770
+ const previousBox = { ...currentBox, x: previous.x + offsetX };
1771
+ const movingRight = dx > 0;
1772
+ const sweptLeft = Math.min(previousBox.x, currentBox.x) - (movingRight ? 0 : MASCOT_COLLISION_LOOKAHEAD);
1773
+ const sweptRight = Math.max(previousBox.x + previousBox.width, currentBox.x + currentBox.width) + (movingRight ? MASCOT_COLLISION_LOOKAHEAD : 0);
1774
+ const previousCenterX = previousBox.x + previousBox.width / 2;
1775
+ const collision = siblings.some((sibling) => {
1776
+ if (sibling.id === this.id) return false;
1777
+ const siblingCenterX = sibling.x + sibling.width / 2;
1778
+ const isAhead = movingRight ? siblingCenterX >= previousCenterX : siblingCenterX <= previousCenterX;
1779
+ const overlapsVertically = currentBox.y < sibling.y + sibling.height && sibling.y < currentBox.y + currentBox.height;
1780
+ const crossesHorizontally = sweptLeft <= sibling.x + sibling.width && sibling.x <= sweptRight;
1781
+ const wasAlreadyOverlapping = overlapsVertically && previousBox.x < sibling.x + sibling.width && sibling.x < previousBox.x + previousBox.width;
1782
+ return isAhead && overlapsVertically && crossesHorizontally && !wasAlreadyOverlapping;
1783
+ });
1784
+ if (!collision) return;
1785
+ this.state.x = previous.x;
1786
+ this.state.vx = 0;
1787
+ this.state.lookRight = !this.state.lookRight;
1788
+ }
1740
1789
  isOutsideVisibleBounds(bounds) {
1741
1790
  const sprite = this.spec.sprites[this.state.sprite];
1742
1791
  const width = typeof sprite === "object" && "width" in sprite && sprite.width !== void 0 ? sprite.width : 128;
@@ -2158,7 +2207,9 @@ var ShimejiEngine = class {
2158
2207
  this.lastFrameTime = timestamp;
2159
2208
  const delta = Math.max(0, Math.min(rawDelta, this.options.maxDeltaTime));
2160
2209
  const { bounds, platforms } = this.readFrameGeometry();
2161
- for (const mascot of [...this.mascots.values()]) mascot.tick(delta, bounds, platforms);
2210
+ const mascots = [...this.mascots.values()];
2211
+ const collisionBoxes = mascots.map((mascot) => mascot.collisionBox());
2212
+ for (const mascot of mascots) mascot.tick(delta, bounds, platforms, collisionBoxes);
2162
2213
  this.emitState();
2163
2214
  this.animationFrame = this.container.ownerDocument.defaultView?.requestAnimationFrame(this.onAnimationFrame);
2164
2215
  };