canvasengine 2.0.0-beta.53 → 2.0.0-beta.56

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": "canvasengine",
3
- "version": "2.0.0-beta.53",
3
+ "version": "2.0.0-beta.56",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -320,6 +320,33 @@ export function DisplayObject(extendClass) {
320
320
  props.maskOf.componentInstance.mask = this as any;
321
321
  }
322
322
  }
323
+ if (props.shadowCaster !== undefined) {
324
+ const shadowCasterValue = (props.shadowCaster as any)?.value ?? props.shadowCaster;
325
+ if (
326
+ shadowCasterValue &&
327
+ typeof shadowCasterValue === "object" &&
328
+ !Array.isArray(shadowCasterValue)
329
+ ) {
330
+ const current = ((this as any).shadowCaster ?? {}) as Record<string, unknown>;
331
+ (this as any).shadowCaster = { ...current, ...shadowCasterValue };
332
+ } else {
333
+ (this as any).shadowCaster = shadowCasterValue;
334
+ }
335
+ }
336
+ if (props.footprintCaster !== undefined) {
337
+ const footprintCasterValue =
338
+ (props.footprintCaster as any)?.value ?? props.footprintCaster;
339
+ if (
340
+ footprintCasterValue &&
341
+ typeof footprintCasterValue === "object" &&
342
+ !Array.isArray(footprintCasterValue)
343
+ ) {
344
+ const current = ((this as any).footprintCaster ?? {}) as Record<string, unknown>;
345
+ (this as any).footprintCaster = { ...current, ...footprintCasterValue };
346
+ } else {
347
+ (this as any).footprintCaster = footprintCasterValue;
348
+ }
349
+ }
323
350
  if (props.blendMode) this.blendMode = props.blendMode;
324
351
  if (props.filterArea) this.filterArea = props.filterArea;
325
352
  const currentFilters = this.filters || [];
@@ -58,13 +58,21 @@ type AnimationDataFrames = {
58
58
  data: TextureOptionsMerging;
59
59
  };
60
60
 
61
+ export type HitboxAnchorMode = "top-left" | "center" | "foot";
62
+
63
+ export type Hitbox = {
64
+ w: number;
65
+ h: number;
66
+ anchorMode?: HitboxAnchorMode;
67
+ };
68
+
61
69
  export enum StandardAnimation {
62
70
  Stand = "stand",
63
71
  Walk = "walk",
64
72
  }
65
73
 
66
74
  export class CanvasSprite extends DisplayObject(PixiSprite) {
67
- public hitbox: { w: number; h: number };
75
+ public hitbox: Hitbox | null = null;
68
76
  public applyTransform: (
69
77
  frame: FrameOptionsMerging,
70
78
  data: TextureOptionsMerging,
@@ -442,7 +450,9 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
442
450
  this.play(this.sheetCurrentAnimation, [this.sheetParams]);
443
451
  }
444
452
 
445
- if (props.hitbox) this.hitbox = props.hitbox.value ?? props.hitbox;
453
+ if (props.hitbox !== undefined) {
454
+ this.hitbox = this.normalizeHitbox(props.hitbox);
455
+ }
446
456
 
447
457
  if (props.scaleMode) this.baseTexture.scaleMode = props.scaleMode;
448
458
  else if (props.image && this.fullProps.rectangle === undefined) {
@@ -470,6 +480,10 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
470
480
  });
471
481
  }
472
482
  }
483
+
484
+ if (this.hitbox && !this.spritesheet) {
485
+ this.applyHitboxAnchor(this.texture.width, this.texture.height);
486
+ }
473
487
  }
474
488
 
475
489
  async onDestroy(parent: Element, afterDestroy: () => void): Promise<void> {
@@ -669,27 +683,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
669
683
  }
670
684
 
671
685
  const realSize = getVal<"spriteRealSize">("spriteRealSize");
672
- const heightOfSprite =
673
- typeof realSize == "number" ? realSize : realSize?.height;
674
- const widthOfSprite =
675
- typeof realSize == "number" ? realSize : realSize?.width;
676
-
677
-
678
- const applyAnchorBySize = () => {
679
- if (heightOfSprite && this.hitbox) {
680
- const { spriteWidth, spriteHeight } = data;
681
- const w = (spriteWidth - this.hitbox.w) / 2 / spriteWidth;
682
- const gap = (spriteHeight - heightOfSprite) / 2;
683
- const h = (spriteHeight - this.hitbox.h - gap) / spriteHeight;
684
- this.anchor.set(w, h);
685
- }
686
- };
687
686
 
688
687
  if (frame.sound) {
689
688
  //RpgSound.get(frame.sound).play()
690
689
  }
691
690
 
692
- applyAnchorBySize();
691
+ this.applyHitboxAnchor(data.spriteWidth, data.spriteHeight, realSize);
693
692
 
694
693
  applyTransform("anchor");
695
694
  applyTransform("scale");
@@ -717,6 +716,75 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
717
716
  this.frameIndex++;
718
717
  }
719
718
  }
719
+
720
+ private applyHitboxAnchor(
721
+ spriteWidth: number,
722
+ spriteHeight: number,
723
+ realSize?: TextureOptionsMerging["spriteRealSize"]
724
+ ) {
725
+ if (!this.hitbox || !spriteWidth || !spriteHeight) {
726
+ return;
727
+ }
728
+
729
+ const heightOfSprite =
730
+ typeof realSize === "number" ? realSize : realSize?.height;
731
+ const resolvedHeight = heightOfSprite ?? spriteHeight;
732
+ const gap = Math.max(0, (spriteHeight - resolvedHeight) / 2);
733
+ const hitboxTopLeftX = this.clamp(
734
+ (spriteWidth - this.hitbox.w) / 2 / spriteWidth
735
+ );
736
+ const hitboxTopLeftY = this.clamp(
737
+ (spriteHeight - this.hitbox.h - gap) / spriteHeight
738
+ );
739
+ const hitboxCenterX = this.clamp(
740
+ hitboxTopLeftX + this.hitbox.w / 2 / spriteWidth
741
+ );
742
+ const hitboxCenterY = this.clamp(
743
+ hitboxTopLeftY + this.hitbox.h / 2 / spriteHeight
744
+ );
745
+ const footY = this.clamp((spriteHeight - gap) / spriteHeight);
746
+
747
+ let anchorX = hitboxTopLeftX;
748
+ let anchorY = hitboxTopLeftY;
749
+
750
+ switch (this.hitbox.anchorMode ?? "top-left") {
751
+ case "center":
752
+ anchorX = hitboxCenterX;
753
+ anchorY = hitboxCenterY;
754
+ break;
755
+ case "foot":
756
+ anchorX = hitboxCenterX;
757
+ anchorY = footY;
758
+ break;
759
+ case "top-left":
760
+ default:
761
+ break;
762
+ }
763
+
764
+ this.anchor.set(anchorX, anchorY);
765
+ }
766
+
767
+ private normalizeHitbox(hitbox: unknown): Hitbox | null {
768
+ const resolvedHitbox = (hitbox as any)?.value ?? hitbox;
769
+ if (!resolvedHitbox || typeof resolvedHitbox !== "object") {
770
+ return null;
771
+ }
772
+
773
+ const { w, h, anchorMode } = resolvedHitbox as Partial<Hitbox>;
774
+ if (typeof w !== "number" || typeof h !== "number") {
775
+ return null;
776
+ }
777
+
778
+ return {
779
+ w,
780
+ h,
781
+ anchorMode,
782
+ };
783
+ }
784
+
785
+ private clamp(value: number) {
786
+ return Math.min(1, Math.max(0, value));
787
+ }
720
788
  }
721
789
 
722
790
  export interface CanvasSprite extends PixiSprite {
@@ -733,6 +801,7 @@ export interface SpriteProps extends DisplayObjectProps {
733
801
  params?: any;
734
802
  onFinish?: () => void;
735
803
  };
804
+ hitbox?: Hitbox;
736
805
  scaleMode?: number;
737
806
  image?: string;
738
807
  rectangle?: {
@@ -65,6 +65,16 @@ export interface DisplayObjectProps {
65
65
  filters?: any[];
66
66
  blendMode?: SignalOrPrimitive<PIXI.BLEND_MODES>;
67
67
  blur?: SignalOrPrimitive<number>;
68
+ /**
69
+ * Optional metadata used by presets (for example `SpriteShadows`)
70
+ * to mark this display object as a shadow caster.
71
+ */
72
+ shadowCaster?: any;
73
+ /**
74
+ * Optional metadata used by presets (for example `Footprints`)
75
+ * to mark this display object as a footprint caster.
76
+ */
77
+ footprintCaster?: any;
68
78
 
69
79
  // Directives
70
80
  drag?: DragProps;
@@ -653,6 +653,54 @@ export function createComponent(tag: string, props?: Props): Element {
653
653
  child = await child;
654
654
  }
655
655
  if (child instanceof Observable) {
656
+ const mountedFlowElements = new Map<Element, Element>();
657
+
658
+ const mountFlowElement = (element: Element, index?: number) => {
659
+ if (mountedFlowElements.has(element)) {
660
+ return;
661
+ }
662
+
663
+ const routed = routeDomComponent(parent, element);
664
+ mountedFlowElements.set(element, routed);
665
+ onMount(parent, routed, index);
666
+ propagateContext(routed);
667
+ };
668
+
669
+ const syncFlowElements = (nextElements: Set<Element>) => {
670
+ mountedFlowElements.forEach((mounted, source) => {
671
+ if (nextElements.has(source)) {
672
+ return;
673
+ }
674
+ mountedFlowElements.delete(source);
675
+ if (mounted !== source) {
676
+ destroyElement(mounted);
677
+ }
678
+ });
679
+ };
680
+
681
+ const processFlowComponent = (
682
+ component: any,
683
+ nextElements: Set<Element>,
684
+ index?: number
685
+ ) => {
686
+ if (component instanceof Observable) {
687
+ void createElement(parent, component);
688
+ return;
689
+ }
690
+ if (Array.isArray(component)) {
691
+ component.forEach((comp) =>
692
+ processFlowComponent(comp, nextElements, index)
693
+ );
694
+ return;
695
+ }
696
+ if (!isElement(component)) {
697
+ return;
698
+ }
699
+
700
+ nextElements.add(component);
701
+ mountFlowElement(component, index);
702
+ };
703
+
656
704
  // Subscribe to the observable and handle the emitted values
657
705
  const subscription = child.subscribe(
658
706
  (value: any) => {
@@ -668,43 +716,19 @@ export function createComponent(tag: string, props?: Props): Element {
668
716
  } = value;
669
717
 
670
718
  const components = comp.filter((c) => c !== null);
719
+ const nextElements = new Set<Element>();
671
720
  if (prev) {
672
- components.forEach(async (c) => {
721
+ components.forEach((c) => {
673
722
  const index = parent.props.children.indexOf(prev.props.key);
674
- if (c instanceof Observable) {
675
- // Handle observable component recursively
676
- await createElement(parent, c);
677
- } else if (isElement(c)) {
678
- const routed = routeDomComponent(parent, c);
679
- onMount(parent, routed, index + 1);
680
- propagateContext(routed);
681
- }
723
+ processFlowComponent(c, nextElements, index + 1);
682
724
  });
725
+ syncFlowElements(nextElements);
683
726
  return;
684
727
  }
685
- components.forEach(async (component) => {
686
- if (!Array.isArray(component)) {
687
- if (component instanceof Observable) {
688
- // Handle observable component recursively
689
- await createElement(parent, component);
690
- } else if (isElement(component)) {
691
- const routed = routeDomComponent(parent, component);
692
- onMount(parent, routed);
693
- propagateContext(routed);
694
- }
695
- } else {
696
- component.forEach(async (comp) => {
697
- if (comp instanceof Observable) {
698
- // Handle observable component recursively
699
- await createElement(parent, comp);
700
- } else if (isElement(comp)) {
701
- const routed = routeDomComponent(parent, comp);
702
- onMount(parent, routed);
703
- propagateContext(routed);
704
- }
705
- });
706
- }
728
+ components.forEach((component, index) => {
729
+ processFlowComponent(component, nextElements, index);
707
730
  });
731
+ syncFlowElements(nextElements);
708
732
  } else if (isElement(value)) {
709
733
  // Handle direct Element emission
710
734
  const routed = routeDomComponent(parent, value);