@react-shimeji/core 0.1.0 → 0.2.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/index.cjs +151 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -4
- package/dist/index.d.ts +19 -4
- package/dist/index.js +149 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -223,6 +223,8 @@ interface ShimejiEngineOptions {
|
|
|
223
223
|
workAreaClassName?: string;
|
|
224
224
|
/** CSS class added to each generated mascot element. */
|
|
225
225
|
mascotClassName?: string;
|
|
226
|
+
/** DOM elements, or a selector for elements, that mascots can use as platforms. */
|
|
227
|
+
platforms?: string | readonly HTMLElement[];
|
|
226
228
|
/** Optional deterministic random-number source. */
|
|
227
229
|
random?: () => number;
|
|
228
230
|
}
|
|
@@ -269,7 +271,7 @@ interface MascotEnvironment {
|
|
|
269
271
|
floor: EnvironmentEdge;
|
|
270
272
|
/** Alias for the work-area top edge. */
|
|
271
273
|
ceiling: EnvironmentEdge;
|
|
272
|
-
/**
|
|
274
|
+
/** Most relevant DOM platform for page-element actions. */
|
|
273
275
|
activeIE: EnvironmentRectangle & {
|
|
274
276
|
visible: boolean;
|
|
275
277
|
};
|
|
@@ -332,6 +334,7 @@ declare class ShimejiEngine {
|
|
|
332
334
|
private nextMascotId;
|
|
333
335
|
private destroyed;
|
|
334
336
|
private initialized;
|
|
337
|
+
private platformSource;
|
|
335
338
|
/** Creates and initializes an engine inside a host DOM element. */
|
|
336
339
|
constructor(container: HTMLElement, options?: ShimejiEngineOptions);
|
|
337
340
|
/** Starts the clock and global listeners. Calling this method more than once is harmless. */
|
|
@@ -352,12 +355,15 @@ declare class ShimejiEngine {
|
|
|
352
355
|
getState(): MascotState[];
|
|
353
356
|
/** Subscribes to a typed engine event and returns an unsubscribe function. */
|
|
354
357
|
on<K extends keyof ShimejiEngineEventMap>(event: K, listener: ShimejiEventListener<K>): () => void;
|
|
358
|
+
/** Replaces the DOM elements (or selector) exposed to mascots as platforms. */
|
|
359
|
+
setPlatforms(platforms: string | readonly HTMLElement[]): void;
|
|
355
360
|
/** Stops animation and timers, removes listeners and DOM, and revokes all object URLs. */
|
|
356
361
|
destroy(): void;
|
|
357
362
|
/** Returns whether this engine has completed permanent teardown. */
|
|
358
363
|
isDestroyed(): boolean;
|
|
359
364
|
private readonly onAnimationFrame;
|
|
360
365
|
private renderAll;
|
|
366
|
+
private readFrameGeometry;
|
|
361
367
|
private emitState;
|
|
362
368
|
private listen;
|
|
363
369
|
private assertAlive;
|
|
@@ -446,12 +452,21 @@ declare function isOnLeft(point: Point, rectangle: Rectangle, tolerance?: number
|
|
|
446
452
|
/** Returns whether a point lies on the right edge of a rectangle. */
|
|
447
453
|
declare function isOnRight(point: Point, rectangle: Rectangle, tolerance?: number): boolean;
|
|
448
454
|
/** Returns whether an anchor satisfies an action's boundary requirement. */
|
|
449
|
-
declare function isOnBorder(state: MascotState, bounds: Rectangle, border: "Floor" | "Wall" | "Ceiling" | undefined): boolean;
|
|
455
|
+
declare function isOnBorder(state: MascotState, bounds: Rectangle, border: "Floor" | "Wall" | "Ceiling" | undefined, platform?: Rectangle): boolean;
|
|
450
456
|
/** Advances ballistic motion and clamps the mascot to the work-area boundaries. */
|
|
451
|
-
declare function applyGravity(state: MascotState, bounds: Rectangle, frameScale: number, gravity: number, resistanceX?: number, resistanceY?: number): boolean;
|
|
457
|
+
declare function applyGravity(state: MascotState, bounds: Rectangle, frameScale: number, gravity: number, resistanceX?: number, resistanceY?: number, platform?: Rectangle): boolean;
|
|
452
458
|
/** Moves a mascot toward a target without overshooting it. */
|
|
453
459
|
declare function moveToward(state: MascotState, target: Point, speed: number, frameScale: number): boolean;
|
|
454
460
|
|
|
461
|
+
/** A platform rectangle paired with the DOM element that produced it. */
|
|
462
|
+
interface PlatformRectangle extends Rectangle {
|
|
463
|
+
element: HTMLElement;
|
|
464
|
+
}
|
|
465
|
+
/** Resolves a platform option into DOM elements, excluding engine-owned nodes. */
|
|
466
|
+
declare function resolvePlatformElements(source: string | readonly HTMLElement[], document: Document, excludedRoot?: HTMLElement): HTMLElement[];
|
|
467
|
+
/** Reads platform bounds once and converts viewport coordinates to work-area coordinates. */
|
|
468
|
+
declare function readPlatformRectangles(elements: readonly HTMLElement[], workAreaRectangle: Pick<DOMRect, "left" | "top">): PlatformRectangle[];
|
|
469
|
+
|
|
455
470
|
/** A resolved image and optional atlas crop for one sprite frame. */
|
|
456
471
|
interface ResolvedSprite {
|
|
457
472
|
/** Browser-loadable image URL. */
|
|
@@ -508,4 +523,4 @@ interface MascotCallbacks {
|
|
|
508
523
|
error(error: Error): void;
|
|
509
524
|
}
|
|
510
525
|
|
|
511
|
-
export { type ActionDefinition, ActionExecutor, type ActionExecutorCallbacks, type ActionExecutorOptions, type ActionType, type AnimationDefinition, BehaviorController, type BehaviorDefinition, type BehaviorType, type BorderType, type CharacterMetadata, type CharacterSource, type CharacterSpec, type EnvironmentEdge, type EnvironmentRectangle, type IndividualSprite, type LegacyCharacterPack, type MascotCallbacks, type MascotDomHandle, type MascotEnvironment, type MascotState, type Point, type Pose, type Rectangle, type ResolvedSprite, ShimejiEngine, type ShimejiEngineEventMap, type ShimejiEngineOptions, type ShimejiEventListener, type SpawnOptions, type SpriteLease, SpriteManager, type SpriteMap, type SpriteRectangle, applyGravity, clamp, conditionsMatch, evaluateExpression, isIndividualSprite, isOnBorder, isOnBottom, isOnLeft, isOnRight, isOnTop, loadCharacter, moveToward, normalizeCharacterSpec, parseActionsXml, parseBehaviorsXml, selectWeighted };
|
|
526
|
+
export { type ActionDefinition, ActionExecutor, type ActionExecutorCallbacks, type ActionExecutorOptions, type ActionType, type AnimationDefinition, BehaviorController, type BehaviorDefinition, type BehaviorType, type BorderType, type CharacterMetadata, type CharacterSource, type CharacterSpec, type EnvironmentEdge, type EnvironmentRectangle, type IndividualSprite, type LegacyCharacterPack, type MascotCallbacks, type MascotDomHandle, type MascotEnvironment, type MascotState, type PlatformRectangle, type Point, type Pose, type Rectangle, type ResolvedSprite, ShimejiEngine, type ShimejiEngineEventMap, type ShimejiEngineOptions, type ShimejiEventListener, type SpawnOptions, type SpriteLease, SpriteManager, type SpriteMap, type SpriteRectangle, applyGravity, clamp, conditionsMatch, evaluateExpression, isIndividualSprite, isOnBorder, isOnBottom, isOnLeft, isOnRight, isOnTop, loadCharacter, moveToward, normalizeCharacterSpec, parseActionsXml, parseBehaviorsXml, readPlatformRectangles, resolvePlatformElements, selectWeighted };
|
package/dist/index.d.ts
CHANGED
|
@@ -223,6 +223,8 @@ interface ShimejiEngineOptions {
|
|
|
223
223
|
workAreaClassName?: string;
|
|
224
224
|
/** CSS class added to each generated mascot element. */
|
|
225
225
|
mascotClassName?: string;
|
|
226
|
+
/** DOM elements, or a selector for elements, that mascots can use as platforms. */
|
|
227
|
+
platforms?: string | readonly HTMLElement[];
|
|
226
228
|
/** Optional deterministic random-number source. */
|
|
227
229
|
random?: () => number;
|
|
228
230
|
}
|
|
@@ -269,7 +271,7 @@ interface MascotEnvironment {
|
|
|
269
271
|
floor: EnvironmentEdge;
|
|
270
272
|
/** Alias for the work-area top edge. */
|
|
271
273
|
ceiling: EnvironmentEdge;
|
|
272
|
-
/**
|
|
274
|
+
/** Most relevant DOM platform for page-element actions. */
|
|
273
275
|
activeIE: EnvironmentRectangle & {
|
|
274
276
|
visible: boolean;
|
|
275
277
|
};
|
|
@@ -332,6 +334,7 @@ declare class ShimejiEngine {
|
|
|
332
334
|
private nextMascotId;
|
|
333
335
|
private destroyed;
|
|
334
336
|
private initialized;
|
|
337
|
+
private platformSource;
|
|
335
338
|
/** Creates and initializes an engine inside a host DOM element. */
|
|
336
339
|
constructor(container: HTMLElement, options?: ShimejiEngineOptions);
|
|
337
340
|
/** Starts the clock and global listeners. Calling this method more than once is harmless. */
|
|
@@ -352,12 +355,15 @@ declare class ShimejiEngine {
|
|
|
352
355
|
getState(): MascotState[];
|
|
353
356
|
/** Subscribes to a typed engine event and returns an unsubscribe function. */
|
|
354
357
|
on<K extends keyof ShimejiEngineEventMap>(event: K, listener: ShimejiEventListener<K>): () => void;
|
|
358
|
+
/** Replaces the DOM elements (or selector) exposed to mascots as platforms. */
|
|
359
|
+
setPlatforms(platforms: string | readonly HTMLElement[]): void;
|
|
355
360
|
/** Stops animation and timers, removes listeners and DOM, and revokes all object URLs. */
|
|
356
361
|
destroy(): void;
|
|
357
362
|
/** Returns whether this engine has completed permanent teardown. */
|
|
358
363
|
isDestroyed(): boolean;
|
|
359
364
|
private readonly onAnimationFrame;
|
|
360
365
|
private renderAll;
|
|
366
|
+
private readFrameGeometry;
|
|
361
367
|
private emitState;
|
|
362
368
|
private listen;
|
|
363
369
|
private assertAlive;
|
|
@@ -446,12 +452,21 @@ declare function isOnLeft(point: Point, rectangle: Rectangle, tolerance?: number
|
|
|
446
452
|
/** Returns whether a point lies on the right edge of a rectangle. */
|
|
447
453
|
declare function isOnRight(point: Point, rectangle: Rectangle, tolerance?: number): boolean;
|
|
448
454
|
/** Returns whether an anchor satisfies an action's boundary requirement. */
|
|
449
|
-
declare function isOnBorder(state: MascotState, bounds: Rectangle, border: "Floor" | "Wall" | "Ceiling" | undefined): boolean;
|
|
455
|
+
declare function isOnBorder(state: MascotState, bounds: Rectangle, border: "Floor" | "Wall" | "Ceiling" | undefined, platform?: Rectangle): boolean;
|
|
450
456
|
/** Advances ballistic motion and clamps the mascot to the work-area boundaries. */
|
|
451
|
-
declare function applyGravity(state: MascotState, bounds: Rectangle, frameScale: number, gravity: number, resistanceX?: number, resistanceY?: number): boolean;
|
|
457
|
+
declare function applyGravity(state: MascotState, bounds: Rectangle, frameScale: number, gravity: number, resistanceX?: number, resistanceY?: number, platform?: Rectangle): boolean;
|
|
452
458
|
/** Moves a mascot toward a target without overshooting it. */
|
|
453
459
|
declare function moveToward(state: MascotState, target: Point, speed: number, frameScale: number): boolean;
|
|
454
460
|
|
|
461
|
+
/** A platform rectangle paired with the DOM element that produced it. */
|
|
462
|
+
interface PlatformRectangle extends Rectangle {
|
|
463
|
+
element: HTMLElement;
|
|
464
|
+
}
|
|
465
|
+
/** Resolves a platform option into DOM elements, excluding engine-owned nodes. */
|
|
466
|
+
declare function resolvePlatformElements(source: string | readonly HTMLElement[], document: Document, excludedRoot?: HTMLElement): HTMLElement[];
|
|
467
|
+
/** Reads platform bounds once and converts viewport coordinates to work-area coordinates. */
|
|
468
|
+
declare function readPlatformRectangles(elements: readonly HTMLElement[], workAreaRectangle: Pick<DOMRect, "left" | "top">): PlatformRectangle[];
|
|
469
|
+
|
|
455
470
|
/** A resolved image and optional atlas crop for one sprite frame. */
|
|
456
471
|
interface ResolvedSprite {
|
|
457
472
|
/** Browser-loadable image URL. */
|
|
@@ -508,4 +523,4 @@ interface MascotCallbacks {
|
|
|
508
523
|
error(error: Error): void;
|
|
509
524
|
}
|
|
510
525
|
|
|
511
|
-
export { type ActionDefinition, ActionExecutor, type ActionExecutorCallbacks, type ActionExecutorOptions, type ActionType, type AnimationDefinition, BehaviorController, type BehaviorDefinition, type BehaviorType, type BorderType, type CharacterMetadata, type CharacterSource, type CharacterSpec, type EnvironmentEdge, type EnvironmentRectangle, type IndividualSprite, type LegacyCharacterPack, type MascotCallbacks, type MascotDomHandle, type MascotEnvironment, type MascotState, type Point, type Pose, type Rectangle, type ResolvedSprite, ShimejiEngine, type ShimejiEngineEventMap, type ShimejiEngineOptions, type ShimejiEventListener, type SpawnOptions, type SpriteLease, SpriteManager, type SpriteMap, type SpriteRectangle, applyGravity, clamp, conditionsMatch, evaluateExpression, isIndividualSprite, isOnBorder, isOnBottom, isOnLeft, isOnRight, isOnTop, loadCharacter, moveToward, normalizeCharacterSpec, parseActionsXml, parseBehaviorsXml, selectWeighted };
|
|
526
|
+
export { type ActionDefinition, ActionExecutor, type ActionExecutorCallbacks, type ActionExecutorOptions, type ActionType, type AnimationDefinition, BehaviorController, type BehaviorDefinition, type BehaviorType, type BorderType, type CharacterMetadata, type CharacterSource, type CharacterSpec, type EnvironmentEdge, type EnvironmentRectangle, type IndividualSprite, type LegacyCharacterPack, type MascotCallbacks, type MascotDomHandle, type MascotEnvironment, type MascotState, type PlatformRectangle, type Point, type Pose, type Rectangle, type ResolvedSprite, ShimejiEngine, type ShimejiEngineEventMap, type ShimejiEngineOptions, type ShimejiEventListener, type SpawnOptions, type SpriteLease, SpriteManager, type SpriteMap, type SpriteRectangle, applyGravity, clamp, conditionsMatch, evaluateExpression, isIndividualSprite, isOnBorder, isOnBottom, isOnLeft, isOnRight, isOnTop, loadCharacter, moveToward, normalizeCharacterSpec, parseActionsXml, parseBehaviorsXml, readPlatformRectangles, resolvePlatformElements, selectWeighted };
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,10 @@ var DomManager = class {
|
|
|
29
29
|
}
|
|
30
30
|
/** Returns the current local work-area rectangle. */
|
|
31
31
|
getBounds() {
|
|
32
|
-
|
|
32
|
+
return this.getBoundsFromClientRectangle(this.workArea.getBoundingClientRect());
|
|
33
|
+
}
|
|
34
|
+
/** Converts a previously-read work-area client rectangle into local bounds. */
|
|
35
|
+
getBoundsFromClientRectangle(rectangle) {
|
|
33
36
|
const width = rectangle.width || this.container.clientWidth || window.innerWidth;
|
|
34
37
|
const height = rectangle.height || this.container.clientHeight || window.innerHeight;
|
|
35
38
|
return { x: 0, y: 0, width, height };
|
|
@@ -597,7 +600,8 @@ var BehaviorController = class {
|
|
|
597
600
|
isOnAnyBoundary(environment) {
|
|
598
601
|
const anchor = environment.mascot.anchor;
|
|
599
602
|
const area = environment.mascot.environment.workArea;
|
|
600
|
-
|
|
603
|
+
const activeIE = environment.mascot.environment.activeIE;
|
|
604
|
+
return area.topBorder.isOn(anchor) || area.leftBorder.isOn(anchor) || area.rightBorder.isOn(anchor) || area.bottomBorder.isOn(anchor) || activeIE.visible && (activeIE.topBorder.isOn(anchor) || activeIE.leftBorder.isOn(anchor) || activeIE.rightBorder.isOn(anchor) || activeIE.bottomBorder.isOn(anchor));
|
|
601
605
|
}
|
|
602
606
|
};
|
|
603
607
|
|
|
@@ -617,17 +621,25 @@ function isOnLeft(point, rectangle, tolerance = 1) {
|
|
|
617
621
|
function isOnRight(point, rectangle, tolerance = 1) {
|
|
618
622
|
return point.y >= rectangle.y - tolerance && point.y <= rectangle.y + rectangle.height + tolerance && Math.abs(point.x - rectangle.x - rectangle.width) <= tolerance;
|
|
619
623
|
}
|
|
620
|
-
function isOnBorder(state, bounds, border) {
|
|
624
|
+
function isOnBorder(state, bounds, border, platform) {
|
|
621
625
|
if (!border) return true;
|
|
622
|
-
if (border === "Floor") return isOnBottom(state, bounds);
|
|
623
|
-
if (border === "Ceiling") return isOnTop(state, bounds);
|
|
624
|
-
return isOnLeft(state, bounds) || isOnRight(state, bounds);
|
|
626
|
+
if (border === "Floor") return isOnBottom(state, bounds) || platform !== void 0 && isOnTop(state, platform);
|
|
627
|
+
if (border === "Ceiling") return isOnTop(state, bounds) || platform !== void 0 && isOnBottom(state, platform);
|
|
628
|
+
return isOnLeft(state, bounds) || isOnRight(state, bounds) || platform !== void 0 && (isOnLeft(state, platform) || isOnRight(state, platform));
|
|
625
629
|
}
|
|
626
|
-
function applyGravity(state, bounds, frameScale, gravity, resistanceX = 0.05, resistanceY = 0.01) {
|
|
627
|
-
|
|
628
|
-
|
|
630
|
+
function applyGravity(state, bounds, frameScale, gravity, resistanceX = 0.05, resistanceY = 0.01, platform) {
|
|
631
|
+
const previousY = state.y;
|
|
632
|
+
const nextX = clamp(state.x + state.vx * frameScale, bounds.x, bounds.x + bounds.width);
|
|
633
|
+
const nextY = clamp(state.y + state.vy * frameScale, bounds.y, bounds.y + bounds.height);
|
|
634
|
+
state.x = nextX;
|
|
635
|
+
state.y = nextY;
|
|
629
636
|
state.vx *= Math.max(0, 1 - resistanceX * frameScale);
|
|
630
637
|
state.vy = state.vy * Math.max(0, 1 - resistanceY * frameScale) + gravity * frameScale;
|
|
638
|
+
if (platform && nextY >= previousY && previousY <= platform.y && nextY >= platform.y && nextX >= platform.x && nextX <= platform.x + platform.width) {
|
|
639
|
+
state.y = platform.y;
|
|
640
|
+
state.vy = 0;
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
631
643
|
if (state.y >= bounds.y + bounds.height) {
|
|
632
644
|
state.y = bounds.y + bounds.height;
|
|
633
645
|
state.vy = 0;
|
|
@@ -664,7 +676,8 @@ function evaluateAction(definition, environment) {
|
|
|
664
676
|
const initialVx = numeric(definition.initialVx, scopedEnvironment);
|
|
665
677
|
let lookRight = environment.mascot.lookRight;
|
|
666
678
|
if (definition.borderType === "Wall") {
|
|
667
|
-
|
|
679
|
+
const { activeIE, workArea } = environment.mascot.environment;
|
|
680
|
+
lookRight = workArea.rightBorder.isOn(environment.mascot.anchor) || activeIE.visible && activeIE.leftBorder.isOn(environment.mascot.anchor);
|
|
668
681
|
} else if (definition.type === "Move" || definition.embedType === "Jump" || definition.embedType === "WalkWithIE") {
|
|
669
682
|
if (targetX !== void 0) lookRight = targetX > environment.mascot.anchor.x;
|
|
670
683
|
} else if (definition.embedType === "Fall" || definition.embedType === "FallWithIE") {
|
|
@@ -792,7 +805,15 @@ var LeafRuntime = class {
|
|
|
792
805
|
case "Fall":
|
|
793
806
|
case "FallWithIE":
|
|
794
807
|
case "Thrown":
|
|
795
|
-
return applyGravity(
|
|
808
|
+
return applyGravity(
|
|
809
|
+
this.state,
|
|
810
|
+
bounds,
|
|
811
|
+
frameScale,
|
|
812
|
+
this.action.gravity ?? this.options.gravity,
|
|
813
|
+
this.action.resistanceX,
|
|
814
|
+
this.action.resistanceY,
|
|
815
|
+
environment.mascot.environment.activeIE.visible ? environment.mascot.environment.activeIE : void 0
|
|
816
|
+
);
|
|
796
817
|
case "Jump": {
|
|
797
818
|
const target = { x: this.action.targetX ?? this.state.x, y: this.action.targetY ?? this.state.y };
|
|
798
819
|
return moveToward(this.state, target, this.action.velocity ?? 20, frameScale);
|
|
@@ -873,7 +894,8 @@ var ActionExecutor = class {
|
|
|
873
894
|
createRuntime(definition, environment, references) {
|
|
874
895
|
if (definition.condition && !evaluateExpression(definition.condition, environment, false)) return new CompleteRuntime();
|
|
875
896
|
const bounds = environment.mascot.environment.workArea;
|
|
876
|
-
|
|
897
|
+
const activeIE = environment.mascot.environment.activeIE;
|
|
898
|
+
if (!isOnBorder(this.state, bounds, definition.borderType, activeIE.visible ? activeIE : void 0)) return new CompleteRuntime();
|
|
877
899
|
if (definition.type === "Reference") {
|
|
878
900
|
if (!definition.name || references.has(definition.name)) return new CompleteRuntime();
|
|
879
901
|
const referenced = this.spec.actions.find((action) => action.name === definition.name);
|
|
@@ -902,17 +924,27 @@ function environmentRectangle(bounds) {
|
|
|
902
924
|
const top = bounds.y;
|
|
903
925
|
const bottom = bounds.y + bounds.height;
|
|
904
926
|
return {
|
|
905
|
-
|
|
927
|
+
x: bounds.x,
|
|
928
|
+
y: bounds.y,
|
|
929
|
+
width: bounds.width,
|
|
930
|
+
height: bounds.height,
|
|
906
931
|
left,
|
|
907
932
|
right,
|
|
908
933
|
top,
|
|
909
934
|
bottom,
|
|
910
|
-
topBorder: edge((point) =>
|
|
911
|
-
leftBorder: edge((point) =>
|
|
912
|
-
rightBorder: edge((point) =>
|
|
913
|
-
bottomBorder: edge((point) =>
|
|
935
|
+
topBorder: edge((point) => isOnTop(point, bounds)),
|
|
936
|
+
leftBorder: edge((point) => isOnLeft(point, bounds)),
|
|
937
|
+
rightBorder: edge((point) => isOnRight(point, bounds)),
|
|
938
|
+
bottomBorder: edge((point) => isOnBottom(point, bounds))
|
|
914
939
|
};
|
|
915
940
|
}
|
|
941
|
+
var PLATFORM_NEARBY_DISTANCE = 400;
|
|
942
|
+
var PLATFORM_EDGE_TOLERANCE = 16;
|
|
943
|
+
function distanceToRectangle(point, rectangle) {
|
|
944
|
+
const dx = Math.max(rectangle.x - point.x, 0, point.x - rectangle.x - rectangle.width);
|
|
945
|
+
const dy = Math.max(rectangle.y - point.y, 0, point.y - rectangle.y - rectangle.height);
|
|
946
|
+
return Math.hypot(dx, dy);
|
|
947
|
+
}
|
|
916
948
|
var Mascot = class {
|
|
917
949
|
/** Creates a mascot and immediately installs its pointer handlers. */
|
|
918
950
|
constructor(id, spec, dom, options, spawnOptions, callbacks) {
|
|
@@ -959,10 +991,13 @@ var Mascot = class {
|
|
|
959
991
|
pointerId;
|
|
960
992
|
pointerDown;
|
|
961
993
|
lastPointer;
|
|
994
|
+
activePlatformElement;
|
|
995
|
+
platforms = [];
|
|
962
996
|
/** Advances behavior, animation, physics, and rendering by one clock tick. */
|
|
963
|
-
tick(deltaMs, bounds) {
|
|
997
|
+
tick(deltaMs, bounds, platforms = []) {
|
|
964
998
|
if (this.destroyed) return;
|
|
965
|
-
|
|
999
|
+
this.platforms = platforms;
|
|
1000
|
+
const environment = this.createEnvironment(bounds, platforms);
|
|
966
1001
|
if (this.state.dragging) {
|
|
967
1002
|
this.dom.render(this.domHandle, this.spec, this.state);
|
|
968
1003
|
return;
|
|
@@ -978,12 +1013,22 @@ var Mascot = class {
|
|
|
978
1013
|
}
|
|
979
1014
|
if (!started) break;
|
|
980
1015
|
}
|
|
981
|
-
|
|
1016
|
+
const activeIE = environment.mascot.environment.activeIE;
|
|
1017
|
+
const wasOnPlatformTop = activeIE.visible && activeIE.topBorder.isOn(this.state);
|
|
1018
|
+
const completed = this.actions.tick(deltaMs, environment, bounds);
|
|
1019
|
+
const remainedNearPlatform = this.state.x >= activeIE.left - PLATFORM_EDGE_TOLERANCE && this.state.x <= activeIE.right + PLATFORM_EDGE_TOLERANCE;
|
|
1020
|
+
if (wasOnPlatformTop && !remainedNearPlatform && !platforms.some((platform) => isOnTop(this.state, platform))) {
|
|
1021
|
+
this.actions.cancel();
|
|
1022
|
+
this.currentBehavior = this.findFallBehavior();
|
|
1023
|
+
if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms));
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
if (!completed) break;
|
|
982
1027
|
if (this.destroyed) return;
|
|
983
|
-
this.currentBehavior = this.behavior.selectNext(this.createEnvironment(bounds));
|
|
984
|
-
if (!this.currentBehavior || !this.startBehavior(this.createEnvironment(bounds))) {
|
|
1028
|
+
this.currentBehavior = this.behavior.selectNext(this.createEnvironment(bounds, platforms));
|
|
1029
|
+
if (!this.currentBehavior || !this.startBehavior(this.createEnvironment(bounds, platforms))) {
|
|
985
1030
|
this.currentBehavior = this.findFallBehavior();
|
|
986
|
-
if (!this.currentBehavior || !this.startBehavior(this.createEnvironment(bounds))) break;
|
|
1031
|
+
if (!this.currentBehavior || !this.startBehavior(this.createEnvironment(bounds, platforms))) break;
|
|
987
1032
|
}
|
|
988
1033
|
deltaMs = 0;
|
|
989
1034
|
}
|
|
@@ -1014,9 +1059,11 @@ var Mascot = class {
|
|
|
1014
1059
|
findFallBehavior() {
|
|
1015
1060
|
return this.behavior.force("Fall") ?? this.behavior.force("\u843D\u4E0B\u3059\u308B");
|
|
1016
1061
|
}
|
|
1017
|
-
createEnvironment(bounds) {
|
|
1062
|
+
createEnvironment(bounds, platforms = this.platforms) {
|
|
1018
1063
|
const workArea = environmentRectangle(bounds);
|
|
1019
1064
|
const inactive = environmentRectangle({ x: -100, y: -100, width: 0, height: 0 });
|
|
1065
|
+
const platform = this.selectActivePlatform(platforms);
|
|
1066
|
+
const activeIE = platform ? { ...environmentRectangle(platform), visible: true } : { ...inactive, visible: false };
|
|
1020
1067
|
return {
|
|
1021
1068
|
gap: 0,
|
|
1022
1069
|
maxCount: 999,
|
|
@@ -1030,11 +1077,30 @@ var Mascot = class {
|
|
|
1030
1077
|
workArea,
|
|
1031
1078
|
floor: workArea.bottomBorder,
|
|
1032
1079
|
ceiling: workArea.topBorder,
|
|
1033
|
-
activeIE
|
|
1080
|
+
activeIE
|
|
1034
1081
|
}
|
|
1035
1082
|
}
|
|
1036
1083
|
};
|
|
1037
1084
|
}
|
|
1085
|
+
selectActivePlatform(platforms) {
|
|
1086
|
+
if (platforms.length === 0) {
|
|
1087
|
+
this.activePlatformElement = void 0;
|
|
1088
|
+
return void 0;
|
|
1089
|
+
}
|
|
1090
|
+
const anchor = this.state;
|
|
1091
|
+
const current = platforms.find((platform) => platform.element === this.activePlatformElement);
|
|
1092
|
+
if (current && (isOnTop(anchor, current, 2) || isOnBottom(anchor, current, 2) || isOnLeft(anchor, current, 2) || isOnRight(anchor, current, 2))) return current;
|
|
1093
|
+
if (this.state.vy >= 0) {
|
|
1094
|
+
const landingPlatform = platforms.filter((platform) => anchor.x >= platform.x && anchor.x <= platform.x + platform.width && platform.y >= anchor.y - 1).sort((left, right) => left.y - right.y)[0];
|
|
1095
|
+
if (landingPlatform) {
|
|
1096
|
+
this.activePlatformElement = landingPlatform.element;
|
|
1097
|
+
return landingPlatform;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
const nearby = [...platforms].map((platform) => ({ platform, distance: distanceToRectangle(anchor, platform) })).filter(({ distance }) => distance <= PLATFORM_NEARBY_DISTANCE).sort((left, right) => left.distance - right.distance)[0]?.platform;
|
|
1101
|
+
this.activePlatformElement = nearby?.element;
|
|
1102
|
+
return nearby;
|
|
1103
|
+
}
|
|
1038
1104
|
installPointerHandlers() {
|
|
1039
1105
|
const element = this.domHandle.element;
|
|
1040
1106
|
const listen = (target, type, listener) => {
|
|
@@ -1085,6 +1151,40 @@ var Mascot = class {
|
|
|
1085
1151
|
}
|
|
1086
1152
|
};
|
|
1087
1153
|
|
|
1154
|
+
// src/platform.ts
|
|
1155
|
+
function resolvePlatformElements(source, document2, excludedRoot) {
|
|
1156
|
+
let elements;
|
|
1157
|
+
if (typeof source === "string") {
|
|
1158
|
+
try {
|
|
1159
|
+
elements = [...document2.querySelectorAll(source)];
|
|
1160
|
+
} catch {
|
|
1161
|
+
return [];
|
|
1162
|
+
}
|
|
1163
|
+
} else {
|
|
1164
|
+
elements = source;
|
|
1165
|
+
}
|
|
1166
|
+
const HTMLElementConstructor = document2.defaultView?.HTMLElement;
|
|
1167
|
+
if (!HTMLElementConstructor) return [];
|
|
1168
|
+
return [...new Set(elements)].filter(
|
|
1169
|
+
(element) => element instanceof HTMLElementConstructor && element.isConnected && (!excludedRoot || !excludedRoot.contains(element))
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
function readPlatformRectangles(elements, workAreaRectangle) {
|
|
1173
|
+
const rectangles = [];
|
|
1174
|
+
for (const element of elements) {
|
|
1175
|
+
const rectangle = element.getBoundingClientRect();
|
|
1176
|
+
if (rectangle.width <= 0 || rectangle.height <= 0) continue;
|
|
1177
|
+
rectangles.push({
|
|
1178
|
+
element,
|
|
1179
|
+
x: rectangle.left - workAreaRectangle.left,
|
|
1180
|
+
y: rectangle.top - workAreaRectangle.top,
|
|
1181
|
+
width: rectangle.width,
|
|
1182
|
+
height: rectangle.height
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
return rectangles;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1088
1188
|
// src/sprite.ts
|
|
1089
1189
|
function dataUriToBlob(source) {
|
|
1090
1190
|
const match = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(source);
|
|
@@ -1165,7 +1265,8 @@ var defaults = {
|
|
|
1165
1265
|
gravity: 2,
|
|
1166
1266
|
maxDeltaTime: 100,
|
|
1167
1267
|
workAreaClassName: "",
|
|
1168
|
-
mascotClassName: ""
|
|
1268
|
+
mascotClassName: "",
|
|
1269
|
+
platforms: []
|
|
1169
1270
|
};
|
|
1170
1271
|
var ShimejiEngine = class {
|
|
1171
1272
|
/** Creates and initializes an engine inside a host DOM element. */
|
|
@@ -1173,6 +1274,7 @@ var ShimejiEngine = class {
|
|
|
1173
1274
|
this.container = container;
|
|
1174
1275
|
if (!container) throw new TypeError("ShimejiEngine requires a container element");
|
|
1175
1276
|
this.options = { ...defaults, ...options, random: options.random };
|
|
1277
|
+
this.platformSource = this.options.platforms;
|
|
1176
1278
|
this.originalContainerPosition = container.style.position;
|
|
1177
1279
|
this.adjustedContainerPosition = getComputedStyle(container).position === "static";
|
|
1178
1280
|
if (this.adjustedContainerPosition) container.style.position = "relative";
|
|
@@ -1196,6 +1298,7 @@ var ShimejiEngine = class {
|
|
|
1196
1298
|
nextMascotId = 1;
|
|
1197
1299
|
destroyed = false;
|
|
1198
1300
|
initialized = false;
|
|
1301
|
+
platformSource;
|
|
1199
1302
|
/** Starts the clock and global listeners. Calling this method more than once is harmless. */
|
|
1200
1303
|
initialize() {
|
|
1201
1304
|
this.assertAlive();
|
|
@@ -1237,12 +1340,12 @@ var ShimejiEngine = class {
|
|
|
1237
1340
|
this.assertAlive();
|
|
1238
1341
|
const spec = this.specs.get(characterId);
|
|
1239
1342
|
if (!spec) throw new Error(`Character '${characterId}' is not registered`);
|
|
1240
|
-
const bounds = this.
|
|
1343
|
+
const { bounds, platforms } = this.readFrameGeometry();
|
|
1241
1344
|
const random = this.options.random ?? Math.random;
|
|
1242
1345
|
const spawnOptions = {
|
|
1243
1346
|
...position,
|
|
1244
1347
|
x: position.x ?? bounds.x + random() * bounds.width,
|
|
1245
|
-
y: position.y ?? bounds.y
|
|
1348
|
+
y: position.y ?? bounds.y
|
|
1246
1349
|
};
|
|
1247
1350
|
const id = `shimeji-${this.nextMascotId++}`;
|
|
1248
1351
|
const mascot = new Mascot(id, spec, this.dom, this.options, spawnOptions, {
|
|
@@ -1258,7 +1361,7 @@ var ShimejiEngine = class {
|
|
|
1258
1361
|
error: (error) => this.events.emit("error", error)
|
|
1259
1362
|
});
|
|
1260
1363
|
this.mascots.set(id, mascot);
|
|
1261
|
-
mascot.tick(0, bounds);
|
|
1364
|
+
mascot.tick(0, bounds, platforms);
|
|
1262
1365
|
const state = mascot.snapshot();
|
|
1263
1366
|
this.events.emit("spawn", state);
|
|
1264
1367
|
this.emitState();
|
|
@@ -1290,6 +1393,11 @@ var ShimejiEngine = class {
|
|
|
1290
1393
|
this.assertAlive();
|
|
1291
1394
|
return this.events.on(event, listener);
|
|
1292
1395
|
}
|
|
1396
|
+
/** Replaces the DOM elements (or selector) exposed to mascots as platforms. */
|
|
1397
|
+
setPlatforms(platforms) {
|
|
1398
|
+
this.assertAlive();
|
|
1399
|
+
this.platformSource = platforms;
|
|
1400
|
+
}
|
|
1293
1401
|
/** Stops animation and timers, removes listeners and DOM, and revokes all object URLs. */
|
|
1294
1402
|
destroy() {
|
|
1295
1403
|
if (this.destroyed) return;
|
|
@@ -1317,14 +1425,20 @@ var ShimejiEngine = class {
|
|
|
1317
1425
|
const rawDelta = this.lastFrameTime === void 0 ? this.options.frameDuration : timestamp - this.lastFrameTime;
|
|
1318
1426
|
this.lastFrameTime = timestamp;
|
|
1319
1427
|
const delta = Math.max(0, Math.min(rawDelta, this.options.maxDeltaTime));
|
|
1320
|
-
const bounds = this.
|
|
1321
|
-
for (const mascot of [...this.mascots.values()]) mascot.tick(delta, bounds);
|
|
1428
|
+
const { bounds, platforms } = this.readFrameGeometry();
|
|
1429
|
+
for (const mascot of [...this.mascots.values()]) mascot.tick(delta, bounds, platforms);
|
|
1322
1430
|
this.emitState();
|
|
1323
1431
|
this.animationFrame = requestAnimationFrame(this.onAnimationFrame);
|
|
1324
1432
|
};
|
|
1325
1433
|
renderAll() {
|
|
1326
|
-
const bounds = this.
|
|
1327
|
-
for (const mascot of this.mascots.values()) mascot.tick(0, bounds);
|
|
1434
|
+
const { bounds, platforms } = this.readFrameGeometry();
|
|
1435
|
+
for (const mascot of this.mascots.values()) mascot.tick(0, bounds, platforms);
|
|
1436
|
+
}
|
|
1437
|
+
readFrameGeometry() {
|
|
1438
|
+
const workAreaRectangle = this.dom.workArea.getBoundingClientRect();
|
|
1439
|
+
const bounds = this.dom.getBoundsFromClientRectangle(workAreaRectangle);
|
|
1440
|
+
const elements = resolvePlatformElements(this.platformSource, this.container.ownerDocument, this.dom.workArea);
|
|
1441
|
+
return { bounds, platforms: readPlatformRectangles(elements, workAreaRectangle) };
|
|
1328
1442
|
}
|
|
1329
1443
|
emitState() {
|
|
1330
1444
|
this.events.emit("statechange", this.getState());
|
|
@@ -1357,6 +1471,8 @@ export {
|
|
|
1357
1471
|
normalizeCharacterSpec,
|
|
1358
1472
|
parseActionsXml,
|
|
1359
1473
|
parseBehaviorsXml,
|
|
1474
|
+
readPlatformRectangles,
|
|
1475
|
+
resolvePlatformElements,
|
|
1360
1476
|
selectWeighted
|
|
1361
1477
|
};
|
|
1362
1478
|
//# sourceMappingURL=index.js.map
|