ecspresso 0.9.0 → 0.10.0
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/README.md +36 -0
- package/dist/bundles/renderers/renderer2D.d.ts +4 -4
- package/dist/bundles/renderers/renderer2D.js +2 -2
- package/dist/bundles/renderers/renderer2D.js.map +4 -8
- package/dist/bundles/utils/bounds.d.ts +2 -2
- package/dist/bundles/utils/bounds.js +4 -0
- package/dist/bundles/utils/bounds.js.map +10 -0
- package/dist/bundles/utils/collision.d.ts +2 -2
- package/dist/bundles/utils/collision.js +4 -0
- package/dist/bundles/utils/collision.js.map +10 -0
- package/dist/bundles/utils/input.d.ts +133 -0
- package/dist/bundles/utils/input.js +4 -0
- package/dist/bundles/utils/input.js.map +10 -0
- package/dist/bundles/utils/movement.d.ts +2 -2
- package/dist/bundles/utils/movement.js +4 -0
- package/dist/bundles/utils/movement.js.map +10 -0
- package/dist/bundles/utils/timers.d.ts +2 -2
- package/dist/bundles/utils/timers.js +2 -2
- package/dist/bundles/utils/timers.js.map +4 -6
- package/dist/bundles/utils/transform.d.ts +2 -2
- package/dist/bundles/utils/transform.js +4 -0
- package/dist/bundles/utils/transform.js.map +10 -0
- package/dist/ecspresso.d.ts +19 -2
- package/dist/entity-manager.d.ts +5 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +7 -7
- package/dist/reactive-query-manager.d.ts +22 -3
- package/dist/system-builder.d.ts +7 -3
- package/dist/types.d.ts +15 -5
- package/package.json +22 -2
package/README.md
CHANGED
|
@@ -691,6 +691,7 @@ const game = ECSpresso.create<GameComponents, {}, GameResources>()
|
|
|
691
691
|
|
|
692
692
|
| Bundle | Import | Default Phase | Description |
|
|
693
693
|
|--------|--------|---------------|-------------|
|
|
694
|
+
| **Input** | `ecspresso/bundles/utils/input` | `preUpdate` | Frame-accurate keyboard/pointer input with action mapping |
|
|
694
695
|
| **Timers** | `ecspresso/bundles/utils/timers` | `preUpdate` | ECS-native timers with event-based completion |
|
|
695
696
|
| **Movement** | `ecspresso/bundles/utils/movement` | `fixedUpdate` | Velocity-based movement integration |
|
|
696
697
|
| **Transform** | `ecspresso/bundles/utils/transform` | `postUpdate` | Hierarchical transform propagation (local/world transforms) |
|
|
@@ -700,6 +701,41 @@ const game = ECSpresso.create<GameComponents, {}, GameResources>()
|
|
|
700
701
|
|
|
701
702
|
Each bundle accepts a `phase` option to override its default.
|
|
702
703
|
|
|
704
|
+
### Input Bundle
|
|
705
|
+
|
|
706
|
+
The input bundle provides frame-accurate keyboard, pointer (mouse + touch via PointerEvent), and named action mapping. It's a resource-only bundle — input is polled via the `inputState` resource. DOM events are accumulated between frames and snapshotted once per frame, so all systems see consistent state.
|
|
707
|
+
|
|
708
|
+
```typescript
|
|
709
|
+
import {
|
|
710
|
+
createInputBundle, defineActionMap,
|
|
711
|
+
type InputResourceTypes, type KeyCode
|
|
712
|
+
} from 'ecspresso/bundles/utils/input';
|
|
713
|
+
|
|
714
|
+
interface Resources extends InputResourceTypes {}
|
|
715
|
+
|
|
716
|
+
const world = ECSpresso.create<Components, Events, Resources>()
|
|
717
|
+
.withBundle(createInputBundle({
|
|
718
|
+
actions: defineActionMap({
|
|
719
|
+
jump: { keys: [' ', 'ArrowUp'] },
|
|
720
|
+
shoot: { keys: ['z'], buttons: [0] },
|
|
721
|
+
moveLeft: { keys: ['a', 'ArrowLeft'] },
|
|
722
|
+
moveRight: { keys: ['d', 'ArrowRight'] },
|
|
723
|
+
}),
|
|
724
|
+
}))
|
|
725
|
+
.build();
|
|
726
|
+
|
|
727
|
+
// In a system:
|
|
728
|
+
const input = ecs.getResource('inputState');
|
|
729
|
+
if (input.actions.justActivated('jump')) { /* ... */ }
|
|
730
|
+
if (input.keyboard.isDown('ArrowRight')) { /* ... */ }
|
|
731
|
+
if (input.pointer.justPressed(0)) { /* ... */ }
|
|
732
|
+
|
|
733
|
+
// Runtime remapping
|
|
734
|
+
input.setActionMap({ jump: { keys: ['w'] } });
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
Key values use the `KeyCode` type — a union of all standard `KeyboardEvent.key` values — providing autocomplete and compile-time validation. Note that the space bar key is `' '` (a space character), not `'Space'`.
|
|
738
|
+
|
|
703
739
|
### Timer Bundle
|
|
704
740
|
|
|
705
741
|
The timer bundle provides ECS-native timers that follow the "data, not callbacks" philosophy. Timers are components processed each frame, with optional event-based completion notifications.
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* This bundle includes transform propagation automatically.
|
|
8
8
|
*/
|
|
9
9
|
import type { Application, ApplicationOptions, Container, Sprite, Graphics } from 'pixi.js';
|
|
10
|
-
import Bundle from '
|
|
11
|
-
import { type LocalTransform, type WorldTransform, type TransformComponentTypes, type TransformBundleOptions } from '
|
|
12
|
-
import { type BoundsRect } from '
|
|
10
|
+
import { Bundle } from 'ecspresso';
|
|
11
|
+
import { type LocalTransform, type WorldTransform, type TransformComponentTypes, type TransformBundleOptions } from 'ecspresso/bundles/utils/transform';
|
|
12
|
+
import { type BoundsRect } from 'ecspresso/bundles/utils/bounds';
|
|
13
13
|
export type { LocalTransform, WorldTransform, TransformComponentTypes };
|
|
14
14
|
export type { BoundsRect };
|
|
15
|
-
export { createTransform, createLocalTransform, createWorldTransform } from '
|
|
15
|
+
export { createTransform, createLocalTransform, createWorldTransform } from 'ecspresso/bundles/utils/transform';
|
|
16
16
|
/**
|
|
17
17
|
* Visibility and alpha component
|
|
18
18
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var w=((J)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(J,{get:(Q,U)=>(typeof require<"u"?require:Q)[U]}):J)(function(J){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+J+'" is not supported')});class v{_label;_ecspresso;_bundle;queries={};processFunction;detachFunction;initializeFunction;eventHandlers;_priority=0;_phase="update";_isRegistered=!1;_groups=[];_inScreens;_excludeScreens;_requiredAssets;constructor(J,Q=null,U=null){this._label=J;this._ecspresso=Q;this._bundle=U}get label(){return this._label}get bundle(){return this._bundle}get ecspresso(){return this._ecspresso}_autoRegister(){if(this._isRegistered||!this._ecspresso)return;let J=this._buildSystemObject();L(J,this._ecspresso),this._isRegistered=!0}_buildSystemObject(){return this._createSystemObject()}_createSystemObject(){let J={label:this._label,entityQueries:this.queries,priority:this._priority,phase:this._phase};if(this.processFunction)J.process=this.processFunction;if(this.detachFunction)J.onDetach=this.detachFunction;if(this.initializeFunction)J.onInitialize=this.initializeFunction;if(this.eventHandlers)J.eventHandlers=this.eventHandlers;if(this._groups.length>0)J.groups=[...this._groups];if(this._inScreens)J.inScreens=this._inScreens;if(this._excludeScreens)J.excludeScreens=this._excludeScreens;if(this._requiredAssets)J.requiredAssets=this._requiredAssets;return J}setPriority(J){return this._priority=J,this}inPhase(J){return this._phase=J,this}inGroup(J){if(!this._groups.includes(J))this._groups.push(J);return this}inScreens(J){return this._inScreens=[...J],this}excludeScreens(J){return this._excludeScreens=[...J],this}requiresAssets(J){return this._requiredAssets=[...J],this}addQuery(J,Q){let U=this;return U.queries={...this.queries,[J]:Q},U}setProcess(J){return this.processFunction=J,this}registerAndContinue(){if(!this._ecspresso)throw Error(`Cannot register system '${this._label}': SystemBuilder is not attached to an ECSpresso instance. Use Bundle.addSystem() or ECSpresso.addSystem() instead.`);return this._autoRegister(),this._ecspresso}and(){if(this._ecspresso)return this._autoRegister(),this._ecspresso;if(this._bundle)return this._bundle;throw Error(`Cannot use and() on system '${this._label}': not attached to ECSpresso or Bundle.`)}setOnDetach(J){return this.detachFunction=J,this}setOnInitialize(J){return this.initializeFunction=J,this}setEventHandlers(J){return this.eventHandlers=J,this}build(J){let Q=this._createSystemObject();if(this._ecspresso)L(Q,this._ecspresso);if(J)L(Q,J);return this}}function L(J,Q){Q._registerSystem(J)}function a(J,Q){return new v(J,Q)}function I(J,Q){return new v(J,null,Q)}function b(){return`bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,9)}`}class A{_systems=[];_resources=new Map;_assets=new Map;_assetGroups=new Map;_screens=new Map;_id;constructor(J){this._id=J||b()}get id(){return this._id}set id(J){this._id=J}addSystem(J){if(typeof J==="string"){let Q=I(J,this);return this._systems.push(Q),Q}else return this._systems.push(J),J}addResource(J,Q){return this._resources.set(J,Q),this}addAsset(J,Q,U){return this._assets.set(J,{loader:Q,eager:U?.eager??!0,group:U?.group}),this}addAssetGroup(J,Q){let U=new Map;for(let[K,z]of Object.entries(Q))U.set(K,z),this._assets.set(K,{loader:z,eager:!1,group:J});return this._assetGroups.set(J,U),this}addScreen(J,Q){return this._screens.set(J,Q),this}getAssets(){return new Map(this._assets)}getScreens(){return new Map(this._screens)}_setResource(J,Q){this._resources.set(J,Q)}_setAsset(J,Q){this._assets.set(J,Q)}_setScreen(J,Q){this._screens.set(J,Q)}getSystems(){return this._systems.map((J)=>J.build())}registerSystemsWithEcspresso(J){for(let Q of this._systems)Q.build(J)}getResources(){return new Map(this._resources)}getResource(J){return this._resources.get(J)}getSystemBuilders(){return[...this._systems]}hasResource(J){return this._resources.has(J)}}function x(J,...Q){if(Q.length===0)return new A(J);let U=new A(J);for(let K of Q){for(let z of K.getSystemBuilders())U.addSystem(z);for(let[z,M]of K.getResources().entries())U._setResource(z,M);for(let[z,M]of K.getAssets().entries())U._setAsset(z,M);for(let[z,M]of K.getScreens().entries())U._setScreen(z,M)}return U}function u(J,Q){return{localTransform:{x:J,y:Q,rotation:0,scaleX:1,scaleY:1}}}function y(J,Q){return{worldTransform:{x:J,y:Q,rotation:0,scaleX:1,scaleY:1}}}function m(J,Q,U){let K=U?.scale??U?.scaleX??1,z=U?.scale??U?.scaleY??1,M=U?.rotation??0,W={x:J,y:Q,rotation:M,scaleX:K,scaleY:z};return{localTransform:{...W},worldTransform:{...W}}}function T(J){let{systemGroup:Q="transform",priority:U=500,phase:K="postUpdate"}=J??{},z=new A("transform");return z.addSystem("transform-propagation").setPriority(U).inPhase(K).inGroup(Q).setProcess((M,W,R)=>{c(R)}).and(),z}function c(J){let{changeThreshold:Q,entityManager:U}=J;J.forEachInHierarchy((z,M)=>{let W=U.getComponent(z,"localTransform"),R=U.getComponent(z,"worldTransform");if(!W||!R)return;let k=U.getChangeSeq(z,"localTransform")>Q,q=M!==null&&U.getChangeSeq(M,"worldTransform")>Q;if(!k&&!q)return;if(M===null)j(W,R);else{let G=U.getComponent(M,"worldTransform");if(G)l(G,W,R);else j(W,R)}J.markChanged(z,"worldTransform")});let K=J.getEntitiesWithQuery(["localTransform","worldTransform"]);for(let z of K)if(J.getParent(z.id)===null&&J.getChildren(z.id).length===0){if(!(U.getChangeSeq(z.id,"localTransform")>Q))continue;let{localTransform:R,worldTransform:k}=z.components;j(R,k),J.markChanged(z.id,"worldTransform")}}function j(J,Q){Q.x=J.x,Q.y=J.y,Q.rotation=J.rotation,Q.scaleX=J.scaleX,Q.scaleY=J.scaleY}function l(J,Q,U){let K=Q.x*J.scaleX,z=Q.y*J.scaleY,M=Math.cos(J.rotation),W=Math.sin(J.rotation),R=K*M-z*W,k=K*W+z*M;U.x=J.x+R,U.y=J.y+k,U.rotation=J.rotation+Q.rotation,U.scaleX=J.scaleX*Q.scaleX,U.scaleY=J.scaleY*Q.scaleY}function X(J,Q,U,K){let z={width:J,height:Q};if(U!==void 0)z.x=U;if(K!==void 0)z.y=K;return z}async function p(J){let{Application:Q}=await import("pixi.js"),U=new Q;return await U.init(J),U}var ZJ={x:0,y:0,rotation:0,scaleX:1,scaleY:1},_J={x:0,y:0,rotation:0,scaleX:1,scaleY:1};function Y(J,Q){let U=Q?.scale,K=typeof U==="number"?U:U?.x??1,z=typeof U==="number"?U:U?.y??1;return{x:J?.x??0,y:J?.y??0,rotation:Q?.rotation??0,scaleX:K,scaleY:z}}function B(J,Q){let U=Q?.scale,K=typeof U==="number"?U:U?.x??1,z=typeof U==="number"?U:U?.y??1;return{x:J?.x??0,y:J?.y??0,rotation:Q?.rotation??0,scaleX:K,scaleY:z}}function O(J){return{visible:J?.visible??!0,alpha:J?.alpha}}function $J(J,Q,U){if(U?.anchor)J.anchor.set(U.anchor.x,U.anchor.y);return{sprite:J,localTransform:Y(Q,U),worldTransform:B(Q,U),visible:O(U)}}function zJ(J,Q,U){return{graphics:J,localTransform:Y(Q,U),worldTransform:B(Q,U),visible:O(U)}}function HJ(J,Q,U){return{container:J,localTransform:Y(Q,U),worldTransform:B(Q,U),visible:O(U)}}function DJ(J){let{rootContainer:Q,systemGroup:U="renderer2d",renderSyncPriority:K=500,transform:z,startLoop:M=!0,renderLayers:W=[]}=J,R=new A("renderer2d-internal");if("init"in J&&J.init!==void 0){let{init:_,container:D}=J;R.addResource("pixiApp",async()=>{let F=await p(_);if(D){let H=typeof D==="string"?document.querySelector(D):D;if(H)H.appendChild(F.canvas);else if(typeof D==="string")console.warn(`Renderer2D bundle: container selector "${D}" not found`)}return F}),R.addResource("rootContainer",{dependsOn:["pixiApp"],factory:(F)=>Q??F.getResource("pixiApp").stage}),R.addResource("bounds",{dependsOn:["pixiApp"],factory:(F)=>{let H=F.getResource("pixiApp");return X(H.screen.width,H.screen.height)}})}else{let _=J.app;R.addResource("pixiApp",_),R.addResource("rootContainer",Q??_.stage),R.addResource("bounds",X(_.screen.width,_.screen.height))}let q=new Map,G=new Map,V;function h(_,D){let F=q.get(_);if(F)return F;let H=D.entityManager.getComponent(_,"sprite");if(H)return q.set(_,H),H;let Z=D.entityManager.getComponent(_,"graphics");if(Z)return q.set(_,Z),Z;let $=D.entityManager.getComponent(_,"container");if($)return q.set(_,$),$;return null}function g(_,D){let F=G.get(_);if(F)return F;let H=V(`layer:${_}`);return G.set(_,H),D.addChild(H),H}function C(_,D){let F=D.getResource("rootContainer"),H=D.getParent(_),Z=H!==null?h(H,D):null;if(Z)return Z;let $=D.entityManager.getComponent(_,"renderLayer");if($)return g($,F);return F}function N(_,D,F){let H=C(_,F);if(D.parent!==H)H.addChild(D)}function P(_){let D=q.get(_);if(D)D.removeFromParent(),q.delete(_)}function S(_,D){let F=q.get(_);if(!F)return;let H=C(_,D);if(F.parent!==H)F.removeFromParent(),H.addChild(F)}R.addSystem("renderer2d-sync").setPriority(K).inPhase("render").inGroup(U).addQuery("sprites",{with:["sprite","worldTransform"],changed:["worldTransform"]}).addQuery("graphics",{with:["graphics","worldTransform"],changed:["worldTransform"]}).addQuery("containers",{with:["container","worldTransform"],changed:["worldTransform"]}).setProcess((_,D,F)=>{for(let H of _.sprites){let{sprite:Z,worldTransform:$}=H.components;Z.position.set($.x,$.y),Z.rotation=$.rotation,Z.scale.set($.scaleX,$.scaleY);let E=F.entityManager.getComponent(H.id,"visible");if(E){if(Z.visible=E.visible,E.alpha!==void 0)Z.alpha=E.alpha}}for(let H of _.graphics){let{graphics:Z,worldTransform:$}=H.components;Z.position.set($.x,$.y),Z.rotation=$.rotation,Z.scale.set($.scaleX,$.scaleY);let E=F.entityManager.getComponent(H.id,"visible");if(E){if(Z.visible=E.visible,E.alpha!==void 0)Z.alpha=E.alpha}}for(let H of _.containers){let{container:Z,worldTransform:$}=H.components;Z.position.set($.x,$.y),Z.rotation=$.rotation,Z.scale.set($.scaleX,$.scaleY);let E=F.entityManager.getComponent(H.id,"visible");if(E){if(Z.visible=E.visible,E.alpha!==void 0)Z.alpha=E.alpha}}}).and(),R.addSystem("renderer2d-scene-graph").setPriority(9999).inGroup(U).setOnInitialize(async(_)=>{let D=_.getResource("pixiApp"),F=_.getResource("rootContainer"),{Container:H}=await import("pixi.js");V=(Z)=>{let $=new H;return $.label=Z,$};for(let Z of W){let $=V(`layer:${Z}`);G.set(Z,$),F.addChild($)}if(_.addReactiveQuery("renderer2d-sprites",{with:["sprite"],onEnter:(Z)=>{let $=Z.components.sprite;q.set(Z.id,$),N(Z.id,$,_)},onExit:(Z)=>{P(Z)}}),_.addReactiveQuery("renderer2d-graphics",{with:["graphics"],onEnter:(Z)=>{let $=Z.components.graphics;q.set(Z.id,$),N(Z.id,$,_)},onExit:(Z)=>{P(Z)}}),_.addReactiveQuery("renderer2d-containers",{with:["container"],onEnter:(Z)=>{let $=Z.components.container;q.set(Z.id,$),N(Z.id,$,_)},onExit:(Z)=>{P(Z)}}),_.on("hierarchyChanged",({entityId:Z})=>{S(Z,_)}),_.onComponentAdded("renderLayer",(Z,$)=>{S($.id,_)}),_.onComponentRemoved("renderLayer",(Z,$)=>{S($.id,_)}),D.renderer.on("resize",(Z,$)=>{let E=_.getResource("bounds");E.width=Z,E.height=$}),M)D.ticker.add((Z)=>{_.update(Z.deltaMS/1000)})}).and();let f=T(z);return x("renderer2d",f,R)}export{y as createWorldTransform,m as createTransform,$J as createSpriteComponents,DJ as createRenderer2DBundle,u as createLocalTransform,zJ as createGraphicsComponents,HJ as createContainerComponents,_J as DEFAULT_WORLD_TRANSFORM,ZJ as DEFAULT_LOCAL_TRANSFORM};
|
|
1
|
+
var G=((M)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(M,{get:(Z,J)=>(typeof require<"u"?require:Z)[J]}):M)(function(M){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+M+'" is not supported')});import{Bundle as f,mergeBundles as h}from"ecspresso";import{createTransformBundle as j}from"ecspresso/bundles/utils/transform";import{createBounds as B}from"ecspresso/bundles/utils/bounds";import{createTransform as s,createLocalTransform as r,createWorldTransform as o}from"ecspresso/bundles/utils/transform";async function T(M){let{Application:Z}=await import("pixi.js"),J=new Z;return await J.init(M),J}var b={x:0,y:0,rotation:0,scaleX:1,scaleY:1},m={x:0,y:0,rotation:0,scaleX:1,scaleY:1};function A(M,Z){let J=Z?.scale,E=typeof J==="number"?J:J?.x??1,R=typeof J==="number"?J:J?.y??1;return{x:M?.x??0,y:M?.y??0,rotation:Z?.rotation??0,scaleX:E,scaleY:R}}function L(M,Z){let J=Z?.scale,E=typeof J==="number"?J:J?.x??1,R=typeof J==="number"?J:J?.y??1;return{x:M?.x??0,y:M?.y??0,rotation:Z?.rotation??0,scaleX:E,scaleY:R}}function N(M){return{visible:M?.visible??!0,alpha:M?.alpha}}function y(M,Z,J){if(J?.anchor)M.anchor.set(J.anchor.x,J.anchor.y);return{sprite:M,localTransform:A(Z,J),worldTransform:L(Z,J),visible:N(J)}}function d(M,Z,J){return{graphics:M,localTransform:A(Z,J),worldTransform:L(Z,J),visible:N(J)}}function l(M,Z,J){return{container:M,localTransform:A(Z,J),worldTransform:L(Z,J),visible:N(J)}}function c(M){let{rootContainer:Z,systemGroup:J="renderer2d",renderSyncPriority:E=500,transform:R,startLoop:V=!0,renderLayers:v=[]}=M,k=new f("renderer2d-internal");if("init"in M&&M.init!==void 0){let{init:D,container:Q}=M;k.addResource("pixiApp",async()=>{let U=await T(D);if(Q){let K=typeof Q==="string"?document.querySelector(Q):Q;if(K)K.appendChild(U.canvas);else if(typeof Q==="string")console.warn(`Renderer2D bundle: container selector "${Q}" not found`)}return U}),k.addResource("rootContainer",{dependsOn:["pixiApp"],factory:(U)=>Z??U.getResource("pixiApp").stage}),k.addResource("bounds",{dependsOn:["pixiApp"],factory:(U)=>{let K=U.getResource("pixiApp");return B(K.screen.width,K.screen.height)}})}else{let D=M.app;k.addResource("pixiApp",D),k.addResource("rootContainer",Z??D.stage),k.addResource("bounds",B(D.screen.width,D.screen.height))}let $=new Map,X=new Map,Y;function w(D,Q){let U=$.get(D);if(U)return U;let K=Q.entityManager.getComponent(D,"sprite");if(K)return $.set(D,K),K;let z=Q.entityManager.getComponent(D,"graphics");if(z)return $.set(D,z),z;let H=Q.entityManager.getComponent(D,"container");if(H)return $.set(D,H),H;return null}function P(D,Q){let U=X.get(D);if(U)return U;let K=Y(`layer:${D}`);return X.set(D,K),Q.addChild(K),K}function S(D,Q){let U=Q.getResource("rootContainer"),K=Q.getParent(D),z=K!==null?w(K,Q):null;if(z)return z;let H=Q.entityManager.getComponent(D,"renderLayer");if(H)return P(H,U);return U}function F(D,Q,U){let K=S(D,U);if(Q.parent!==K)K.addChild(Q)}function W(D){let Q=$.get(D);if(Q)Q.removeFromParent(),$.delete(D)}function q(D,Q){let U=$.get(D);if(!U)return;let K=S(D,Q);if(U.parent!==K)U.removeFromParent(),K.addChild(U)}k.addSystem("renderer2d-sync").setPriority(E).inPhase("render").inGroup(J).addQuery("sprites",{with:["sprite","worldTransform"],changed:["worldTransform"]}).addQuery("graphics",{with:["graphics","worldTransform"],changed:["worldTransform"]}).addQuery("containers",{with:["container","worldTransform"],changed:["worldTransform"]}).setProcess((D,Q,U)=>{for(let K of D.sprites){let{sprite:z,worldTransform:H}=K.components;z.position.set(H.x,H.y),z.rotation=H.rotation,z.scale.set(H.scaleX,H.scaleY);let _=U.entityManager.getComponent(K.id,"visible");if(_){if(z.visible=_.visible,_.alpha!==void 0)z.alpha=_.alpha}}for(let K of D.graphics){let{graphics:z,worldTransform:H}=K.components;z.position.set(H.x,H.y),z.rotation=H.rotation,z.scale.set(H.scaleX,H.scaleY);let _=U.entityManager.getComponent(K.id,"visible");if(_){if(z.visible=_.visible,_.alpha!==void 0)z.alpha=_.alpha}}for(let K of D.containers){let{container:z,worldTransform:H}=K.components;z.position.set(H.x,H.y),z.rotation=H.rotation,z.scale.set(H.scaleX,H.scaleY);let _=U.entityManager.getComponent(K.id,"visible");if(_){if(z.visible=_.visible,_.alpha!==void 0)z.alpha=_.alpha}}}).and(),k.addSystem("renderer2d-scene-graph").setPriority(9999).inGroup(J).setOnInitialize(async(D)=>{let Q=D.getResource("pixiApp"),U=D.getResource("rootContainer"),{Container:K}=await import("pixi.js");Y=(z)=>{let H=new K;return H.label=z,H};for(let z of v){let H=Y(`layer:${z}`);X.set(z,H),U.addChild(H)}if(D.addReactiveQuery("renderer2d-sprites",{with:["sprite"],onEnter:(z)=>{let H=z.components.sprite;$.set(z.id,H),F(z.id,H,D)},onExit:(z)=>{W(z)}}),D.addReactiveQuery("renderer2d-graphics",{with:["graphics"],onEnter:(z)=>{let H=z.components.graphics;$.set(z.id,H),F(z.id,H,D)},onExit:(z)=>{W(z)}}),D.addReactiveQuery("renderer2d-containers",{with:["container"],onEnter:(z)=>{let H=z.components.container;$.set(z.id,H),F(z.id,H,D)},onExit:(z)=>{W(z)}}),D.on("hierarchyChanged",({entityId:z})=>{q(z,D)}),D.onComponentAdded("renderLayer",(z,H)=>{q(H.id,D)}),D.onComponentRemoved("renderLayer",(z,H)=>{q(H.id,D)}),Q.renderer.on("resize",(z,H)=>{let _=D.getResource("bounds");_.width=z,_.height=H}),V)Q.ticker.add((z)=>{D.update(z.deltaMS/1000)})}).and();let g=j(R);return h("renderer2d",g,k)}export{o as createWorldTransform,s as createTransform,y as createSpriteComponents,c as createRenderer2DBundle,r as createLocalTransform,d as createGraphicsComponents,l as createContainerComponents,m as DEFAULT_WORLD_TRANSFORM,b as DEFAULT_LOCAL_TRANSFORM};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=BB9BA06A6FF5407264756E2164756E21
|
|
4
4
|
//# sourceMappingURL=renderer2D.js.map
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/
|
|
3
|
+
"sources": ["../src/bundles/renderers/renderer2D.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import Bundle from \"./bundle\";\nimport ECSpresso from \"./ecspresso\";\nimport type { FilteredEntity, System, SystemPhase } from \"./types\";\n\n/**\n * Builder class for creating type-safe ECS Systems with proper query inference\n */\nexport class SystemBuilder<\n\tComponentTypes extends Record<string, any> = Record<string, any>,\n\tEventTypes extends Record<string, any> = Record<string, any>,\n\tResourceTypes extends Record<string, any> = Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {},\n> {\n\tprivate queries: Queries = {} as Queries;\n\tprivate processFunction?: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>;\n\tprivate detachFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate initializeFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate eventHandlers?: {\n\t\t[EventName in keyof EventTypes]?: {\n\t\t\thandler(\n\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\tecs: ECSpresso<\n\t\t\t\t\tComponentTypes,\n\t\t\t\t\tEventTypes,\n\t\t\t\t\tResourceTypes\n\t\t\t\t>,\n\t\t\t): void;\n\t\t};\n\t};\n\tprivate _priority = 0; // Default priority is 0\n\tprivate _phase: SystemPhase = 'update'; // Default phase is 'update'\n\tprivate _isRegistered = false; // Track if system has been auto-registered\n\tprivate _groups: string[] = [];\n\tprivate _inScreens?: string[];\n\tprivate _excludeScreens?: string[];\n\tprivate _requiredAssets?: string[];\n\n\tconstructor(\n\t\tprivate _label: string,\n\t\tprivate _ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t\tprivate _bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t) {}\n\n\tget label() {\n\t\treturn this._label;\n\t}\n\n\t/**\n\t * Returns the associated bundle if one was provided in the constructor\n\t */\n\tget bundle() {\n\t\treturn this._bundle;\n\t}\n\n\t/**\n\t * Returns the associated ECSpresso instance if one was provided in the constructor\n\t */\n\tget ecspresso() {\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Auto-register this system with its ECSpresso instance if not already registered\n\t * @private\n\t */\n\tprivate _autoRegister(): void {\n\t\tif (this._isRegistered || !this._ecspresso) return;\n\t\t\n\t\tconst system = this._buildSystemObject();\n\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\tthis._isRegistered = true;\n\t}\n\n\t/**\n\t * Create the system object without registering it\n\t * @private\n\t */\n\tprivate _buildSystemObject(): System<ComponentTypes, any, any, EventTypes, ResourceTypes> {\n\t\treturn this._createSystemObject();\n\t}\n\n\t/**\n\t * Create a system object with all configured properties\n\t * @private\n\t */\n\tprivate _createSystemObject(): System<ComponentTypes, any, any, EventTypes, ResourceTypes> {\n\t\tconst system: System<ComponentTypes, any, any, EventTypes, ResourceTypes> = {\n\t\t\tlabel: this._label,\n\t\t\tentityQueries: this.queries,\n\t\t\tpriority: this._priority,\n\t\t\tphase: this._phase,\n\t\t};\n\n\t\tif (this.processFunction) {\n\t\t\tsystem.process = this.processFunction;\n\t\t}\n\n\t\tif (this.detachFunction) {\n\t\t\tsystem.onDetach = this.detachFunction;\n\t\t}\n\n\t\tif (this.initializeFunction) {\n\t\t\tsystem.onInitialize = this.initializeFunction;\n\t\t}\n\n\t\tif (this.eventHandlers) {\n\t\t\tsystem.eventHandlers = this.eventHandlers;\n\t\t}\n\n\t\tif (this._groups.length > 0) {\n\t\t\tsystem.groups = [...this._groups];\n\t\t}\n\n\t\tif (this._inScreens) {\n\t\t\tsystem.inScreens = this._inScreens;\n\t\t}\n\n\t\tif (this._excludeScreens) {\n\t\t\tsystem.excludeScreens = this._excludeScreens;\n\t\t}\n\n\t\tif (this._requiredAssets) {\n\t\t\tsystem.requiredAssets = this._requiredAssets;\n\t\t}\n\n\t\treturn system;\n\t}\n\n\t// TODO: Should this be a setter?\n\t/**\n\t * Set the priority of this system. Systems with higher priority values\n\t * execute before those with lower values. Systems with the same priority\n\t * execute in the order they were registered.\n\t * @param priority The priority value (default: 0)\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetPriority(priority: number): this {\n\t\tthis._priority = priority;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the execution phase for this system.\n\t * Systems are grouped by phase and executed in order:\n\t * preUpdate -> fixedUpdate -> update -> postUpdate -> render\n\t * @param phase The phase to assign this system to (default: 'update')\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tinPhase(phase: SystemPhase): this {\n\t\tthis._phase = phase;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add this system to a group. Systems can belong to multiple groups.\n\t * When any group a system belongs to is disabled, the system will be skipped.\n\t * @param groupName The name of the group to add the system to\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tinGroup(groupName: string): this {\n\t\tif (!this._groups.includes(groupName)) {\n\t\t\tthis._groups.push(groupName);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Restrict this system to only run in specified screens.\n\t * System will be skipped during update() when the current screen\n\t * is not in this list.\n\t * @param screens Array of screen names where this system should run\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tinScreens(screens: ReadonlyArray<string>): this {\n\t\tthis._inScreens = [...screens];\n\t\treturn this;\n\t}\n\n\t/**\n\t * Exclude this system from running in specified screens.\n\t * System will be skipped during update() when the current screen\n\t * is in this list.\n\t * @param screens Array of screen names where this system should NOT run\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\texcludeScreens(screens: ReadonlyArray<string>): this {\n\t\tthis._excludeScreens = [...screens];\n\t\treturn this;\n\t}\n\n\t/**\n\t * Require specific assets to be loaded for this system to run.\n\t * System will be skipped during update() if any required asset\n\t * is not loaded.\n\t * @param assets Array of asset keys that must be loaded\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\trequiresAssets(assets: ReadonlyArray<string>): this {\n\t\tthis._requiredAssets = [...assets];\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a query definition to the system\n\t */\n\taddQuery<\n\t\tQueryName extends string,\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never,\n\t\tNewQueries extends Queries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>> =\n\t\t\tQueries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>>\n\t>(\n\t\tname: QueryName,\n\t\tdefinition: {\n\t\t\twith: ReadonlyArray<WithComponents>;\n\t\t\twithout?: ReadonlyArray<WithoutComponents>;\n\t\t\tchanged?: ReadonlyArray<WithComponents>;\n\t\t}\n\t): this extends SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t? SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t: this extends SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t\t? SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t\t: SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, NewQueries> {\n\t\t// Cast is needed because TypeScript can't preserve the type information\n\t\t// when modifying an object property\n\t\tconst newBuilder = this as any;\n\t\tnewBuilder.queries = {\n\t\t\t...this.queries,\n\t\t\t[name]: definition,\n\t\t};\n\t\treturn newBuilder;\n\t}\n\n\t/**\n\t * Set the system's process function that runs each update\n\t * @param process Function to process entities matching the system's queries each update\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetProcess(\n\t\tprocess: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t): this {\n\t\tthis.processFunction = process;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Register this system with its ECSpresso instance and return the ECSpresso for chaining\n\t * This enables seamless method chaining: .registerAndContinue().addSystem(...)\n\t * @returns ECSpresso instance if attached to one, otherwise throws an error\n\t */\n\tregisterAndContinue(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> {\n\t\tif (!this._ecspresso) {\n\t\t\tthrow new Error(`Cannot register system '${this._label}': SystemBuilder is not attached to an ECSpresso instance. Use Bundle.addSystem() or ECSpresso.addSystem() instead.`);\n\t\t}\n\t\t\n\t\tthis._autoRegister();\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Complete this system and return the parent container for seamless chaining\n\t * - For ECSpresso-attached builders: registers the system and returns ECSpresso\n\t * - For Bundle-attached builders: returns the Bundle\n\t * This method is typed via the specialized interfaces (SystemBuilderWithEcspresso, SystemBuilderWithBundle)\n\t */\n\tand(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | Bundle<ComponentTypes, EventTypes, ResourceTypes> {\n\t\tif (this._ecspresso) {\n\t\t\tthis._autoRegister();\n\t\t\treturn this._ecspresso;\n\t\t}\n\n\t\tif (this._bundle) {\n\t\t\treturn this._bundle;\n\t\t}\n\n\t\tthrow new Error(`Cannot use and() on system '${this._label}': not attached to ECSpresso or Bundle.`);\n\t}\n\n\t/**\n\t * Set the onDetach lifecycle hook\n\t * Called when the system is removed from the ECS\n\t * @param onDetach Function to run when this system is detached from the ECS\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnDetach(\n\t\tonDetach: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.detachFunction = onDetach;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the onInitialize lifecycle hook\n\t * Called when the system is initialized via ECSpresso.initialize() method\n\t * @param onInitialize Function to run when this system is initialized\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnInitialize(\n\t\tonInitialize: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.initializeFunction = onInitialize;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set event handlers for the system\n\t * These handlers will be automatically subscribed when the system is attached\n\t * @param handlers Object mapping event names to handler functions\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetEventHandlers(\n\t\thandlers: {\n\t\t\t[EventName in keyof EventTypes]?: {\n\t\t\t\thandler(\n\t\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\t\tecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n\t\t\t\t): void;\n\t\t\t};\n\t\t}\n\t): this {\n\t\tthis.eventHandlers = handlers;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Build the final system object\n\t */\n\tbuild(ecspresso?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tconst system = this._createSystemObject();\n\n\t\tif (this._ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\t}\n\n\t\tif(ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, ecspresso);\n\t\t}\n\n\t\treturn this;\n\t}\n}\n\n/**\n * Helper function to register a system with an ECSpresso instance\n * This handles attaching the system and setting up event handlers\n * @internal Used by SystemBuilder and Bundle\n */\nexport function registerSystemWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tsystem: System<ComponentTypes, any, any, EventTypes, ResourceTypes>,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n) {\n\t// Use the new internal registration method instead of direct property access\n\tecspresso._registerSystem(system);\n}\n\n// Helper type definitions\ntype QueryDefinition<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes = any,\n\tWithoutComponents extends keyof ComponentTypes = any,\n> = {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n\tchanged?: ReadonlyArray<WithComponents>;\n};\n\ntype QueryResults<\n\tComponentTypes,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = {\n\t[QueryName in keyof Queries]: QueryName extends string\n\t\t? FilteredEntity<\n\t\t\tComponentTypes,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, infer W, any> ? W : never,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, any, infer WO> ? WO : never\n\t\t>[]\n\t\t: never;\n};\n\n/**\n * Function signature for system process methods\n * @param queries Results of entity queries defined by the system\n * @param deltaTime Time elapsed since last update in seconds\n * @param ecs The ECSpresso instance providing access to all ECS functionality\n */\ntype ProcessFunction<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = (\n\tqueries: QueryResults<ComponentTypes, Queries>,\n\tdeltaTime: number,\n\tecs: ECSpresso<\n\t\tComponentTypes,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>\n) => void;\n\n/**\n * Type for system initialization functions\n * These can be asynchronous\n */\ntype LifecycleFunction<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n> = (\n\tecs: ECSpresso<\n\t\tComponentTypes,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>,\n) => void | Promise<void>;\n\n/**\n * Create a SystemBuilder attached to an ECSpresso instance\n * Helper function used by ECSpresso.addSystem\n */\nexport function createEcspressoSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tecspresso\n\t) as SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * Create a SystemBuilder attached to a Bundle\n * Helper function used by Bundle.addSystem\n */\nexport function createBundleSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tbundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tnull,\n\t\tbundle\n\t) as SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n// Type interfaces for specialized SystemBuilders\n\n/**\n * SystemBuilder with a guaranteed non-null reference to an ECSpresso instance\n */\nexport interface SystemBuilderWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n\t\n\t/**\n\t * Complete this system and return ECSpresso for seamless chaining\n\t * Automatically registers the system when called\n\t */\n\tand(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * SystemBuilder with a guaranteed non-null reference to a Bundle\n */\nexport interface SystemBuilderWithBundle<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n\n\t/**\n\t * Complete this system and return the Bundle for chaining\n\t * Enables fluent API: bundle.addSystem(...).and().addSystem(...)\n\t */\n\tand(): Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n",
|
|
6
|
-
"import { createBundleSystemBuilder, SystemBuilderWithBundle } from './system-builder';\nimport type ECSpresso from './ecspresso';\nimport type { AssetDefinition } from './asset-types';\nimport type { ScreenDefinition } from './screen-types';\nimport type { BundlesAreCompatible } from './type-utils';\n\n/**\n * Generates a unique ID for a bundle\n */\nfunction generateBundleId(): string {\n\treturn `bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Bundle class that encapsulates a set of components, resources, events, and systems\n * that can be merged into a ECSpresso instance\n */\nexport default class Bundle<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n\tAssetTypes extends Record<string, unknown> = {},\n\tScreenStates extends Record<string, ScreenDefinition<any, any>> = {},\n> {\n\tprivate _systems: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>[] = [];\n\tprivate _resources: Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> = new Map();\n\tprivate _assets: Map<string, AssetDefinition<unknown>> = new Map();\n\tprivate _assetGroups: Map<string, Map<string, () => Promise<unknown>>> = new Map();\n\tprivate _screens: Map<string, ScreenDefinition<any, any>> = new Map();\n\tprivate _id: string;\n\n\tconstructor(id?: string) {\n\t\tthis._id = id || generateBundleId();\n\t}\n\n\t/**\n\t * Get the unique ID of this bundle\n\t */\n\tget id(): string {\n\t\treturn this._id;\n\t}\n\n\t/**\n\t * Set the ID of this bundle\n\t * @internal Used by combineBundles\n\t */\n\tset id(value: string) {\n\t\tthis._id = value;\n\t}\n\n\t/**\n\t * Add a system to this bundle, by label (creating a new builder) or by reusing an existing one\n\t */\n\taddSystem<Q extends Record<string, any>>(builder: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Q>): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Q>;\n\taddSystem(label: string): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, {}>;\n\taddSystem(builderOrLabel: string | SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>) {\n\t\tif (typeof builderOrLabel === 'string') {\n\t\t\tconst system = createBundleSystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(builderOrLabel, this);\n\t\t\tthis._systems.push(system);\n\t\t\treturn system;\n\t\t} else {\n\t\t\tthis._systems.push(builderOrLabel);\n\t\t\treturn builderOrLabel;\n\t\t}\n\t}\n\n\t/**\n\t * Add a resource to this bundle\n\t * @param label The resource key\n\t * @param resource The resource value, a factory function, or a factory with dependencies\n\t */\n\taddResource<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tresource:\n\t\t\t| ResourceTypes[K]\n\t\t\t| ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t\t\t| { dependsOn: readonly string[]; factory: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]> }\n\t) {\n\t\t// We need this cast because TypeScript doesn't recognize that a value of type\n\t\t// ResourceTypes[K] | (() => ResourceTypes[K] | Promise<ResourceTypes[K]>) | { dependsOn, factory }\n\t\t// can be properly assigned to Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]>\n\t\tthis._resources.set(label, resource as unknown as ResourceTypes[K]);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add an asset to this bundle\n\t * @param key The asset key\n\t * @param loader Function that loads and returns the asset\n\t * @param options Optional asset configuration\n\t */\n\taddAsset<K extends string, T>(\n\t\tkey: K,\n\t\tloader: () => Promise<T>,\n\t\toptions?: { eager?: boolean; group?: string }\n\t): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & Record<K, T>, ScreenStates> {\n\t\tthis._assets.set(key, {\n\t\t\tloader,\n\t\t\teager: options?.eager ?? true,\n\t\t\tgroup: options?.group,\n\t\t});\n\t\treturn this as unknown as Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & Record<K, T>, ScreenStates>;\n\t}\n\n\t/**\n\t * Add a group of assets to this bundle\n\t * @param groupName The group name\n\t * @param assets Object mapping asset keys to loader functions\n\t */\n\taddAssetGroup<G extends string, T extends Record<string, () => Promise<unknown>>>(\n\t\tgroupName: G,\n\t\tassets: T\n\t): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & { [K in keyof T]: Awaited<ReturnType<T[K]>> }, ScreenStates> {\n\t\tconst groupAssets = new Map<string, () => Promise<unknown>>();\n\t\tfor (const [key, loader] of Object.entries(assets)) {\n\t\t\tgroupAssets.set(key, loader as () => Promise<unknown>);\n\t\t\tthis._assets.set(key, {\n\t\t\t\tloader: loader as () => Promise<unknown>,\n\t\t\t\teager: false,\n\t\t\t\tgroup: groupName,\n\t\t\t});\n\t\t}\n\t\tthis._assetGroups.set(groupName, groupAssets);\n\t\treturn this as unknown as Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & { [K in keyof T]: Awaited<ReturnType<T[K]>> }, ScreenStates>;\n\t}\n\n\t/**\n\t * Add a screen to this bundle\n\t * @param name The screen name\n\t * @param definition The screen definition\n\t */\n\taddScreen<K extends string, Config extends Record<string, unknown>, State extends Record<string, unknown>>(\n\t\tname: K,\n\t\tdefinition: ScreenDefinition<Config, State>\n\t): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates & Record<K, ScreenDefinition<Config, State>>> {\n\t\tthis._screens.set(name, definition);\n\t\treturn this as unknown as Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates & Record<K, ScreenDefinition<Config, State>>>;\n\t}\n\n\t/**\n\t * Get all asset definitions in this bundle\n\t */\n\tgetAssets(): Map<string, AssetDefinition<unknown>> {\n\t\treturn new Map(this._assets);\n\t}\n\n\t/**\n\t * Get all screen definitions in this bundle\n\t */\n\tgetScreens(): Map<string, ScreenDefinition<any, any>> {\n\t\treturn new Map(this._screens);\n\t}\n\n\t/**\n\t * Internal method to set a resource\n\t * @internal Used by mergeBundles\n\t */\n\t_setResource(key: string, value: unknown): void {\n\t\tthis._resources.set(key as keyof ResourceTypes, value as ResourceTypes[keyof ResourceTypes]);\n\t}\n\n\t/**\n\t * Internal method to set an asset definition\n\t * @internal Used by mergeBundles\n\t */\n\t_setAsset(key: string, definition: AssetDefinition<unknown>): void {\n\t\tthis._assets.set(key, definition);\n\t}\n\n\t/**\n\t * Internal method to set a screen definition\n\t * @internal Used by mergeBundles\n\t */\n\t_setScreen(name: string, definition: ScreenDefinition<any, any>): void {\n\t\tthis._screens.set(name, definition);\n\t}\n\n\t/**\n\t * Get all systems defined in this bundle\n\t * Returns built System objects instead of SystemBuilders\n\t */\n\tgetSystems() {\n\t\treturn this._systems.map(system => system.build());\n\t}\n\n\t/**\n\t * Register all systems in this bundle with an ECSpresso instance\n\t * @internal Used by ECSpresso when adding a bundle\n\t */\n\tregisterSystemsWithEcspresso(ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tfor (const systemBuilder of this._systems) {\n\t\t\tsystemBuilder.build(ecspresso);\n\t\t}\n\t}\n\n\t/**\n\t * Get all resources defined in this bundle\n\t */\n\tgetResources(): Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> {\n\t\treturn new Map(this._resources);\n\t}\n\n\t/**\n\t * Get a specific resource by key\n\t * @param key The resource key\n\t * @returns The resource value or undefined if not found\n\t */\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\treturn this._resources.get(key) as ResourceTypes[K];\n\t}\n\n\t/**\n\t * Get all system builders in this bundle\n\t */\n\tgetSystemBuilders(): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>[] {\n\t\treturn [...this._systems];\n\t}\n\n\t/**\n\t * Check if this bundle has a specific resource\n\t * @param key The resource key to check\n\t * @returns True if the resource exists\n\t */\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resources.has(key);\n\t}\n}\n\n/**\n * Function that merges multiple bundles into a single bundle\n */\nexport function mergeBundles<\n\tC1 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tA1 extends Record<string, unknown>,\n\tS1 extends Record<string, ScreenDefinition<any, any>>,\n\tC2 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR2 extends Record<string, any>,\n\tA2 extends Record<string, unknown>,\n\tS2 extends Record<string, ScreenDefinition<any, any>>,\n>(\n\tid: string,\n\tbundle1: Bundle<C1, E1, R1, A1, S1>,\n\tbundle2: BundlesAreCompatible<C1, C2, E1, E2, R1, R2, A1, A2, S1, S2> extends true\n\t\t? Bundle<C2, E2, R2, A2, S2>\n\t\t: never\n): Bundle<C1 & C2, E1 & E2, R1 & R2, A1 & A2, S1 & S2>;\n\nexport function mergeBundles<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tAssetTypes extends Record<string, unknown>,\n\tScreenStates extends Record<string, ScreenDefinition<any, any>>,\n>(\n\tid: string,\n\t...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>>\n): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>;\n\nexport function mergeBundles(\n\tid: string,\n\t...bundles: Array<Bundle<any, any, any, any, any>>\n): Bundle<any, any, any, any, any> {\n\tif (bundles.length === 0) {\n\t\treturn new Bundle(id);\n\t}\n\n\tconst combined = new Bundle(id);\n\n\tfor (const bundle of bundles) {\n\t\tfor (const system of bundle.getSystemBuilders()) {\n\t\t\t// reuse the full builder so we carry over queries, hooks, and handlers\n\t\t\tcombined.addSystem(system);\n\t\t}\n\n\t\t// Add resources from this bundle\n\t\tfor (const [label, resource] of bundle.getResources().entries()) {\n\t\t\tcombined._setResource(label as string, resource);\n\t\t}\n\n\t\t// Add assets from this bundle\n\t\tfor (const [key, definition] of bundle.getAssets().entries()) {\n\t\t\tcombined._setAsset(key, definition);\n\t\t}\n\n\t\t// Add screens from this bundle\n\t\tfor (const [name, definition] of bundle.getScreens().entries()) {\n\t\t\tcombined._setScreen(name, definition);\n\t\t}\n\t}\n\n\treturn combined;\n}\n",
|
|
7
|
-
"/**\n * Transform Bundle for ECSpresso\n *\n * Provides hierarchical transform propagation following Bevy's Transform/GlobalTransform pattern.\n * LocalTransform is modified by user code; WorldTransform is computed automatically.\n *\n * @see https://docs.rs/bevy/latest/bevy/transform/components/struct.GlobalTransform.html\n */\n\nimport Bundle from '../../bundle';\nimport type { SystemPhase } from '../../types';\nimport type ECSpresso from '../../ecspresso';\n\n// ==================== Component Types ====================\n\n/**\n * Local transform relative to parent (or world if no parent).\n * This is the transform you modify directly.\n */\nexport interface LocalTransform {\n\tx: number;\n\ty: number;\n\trotation: number;\n\tscaleX: number;\n\tscaleY: number;\n}\n\n/**\n * Computed world transform (accumulated from parent chain).\n * Read-only - managed by the transform propagation system.\n */\nexport interface WorldTransform {\n\tx: number;\n\ty: number;\n\trotation: number;\n\tscaleX: number;\n\tscaleY: number;\n}\n\n/**\n * Component types provided by the transform bundle.\n * Extend your component types with this interface.\n *\n * @example\n * ```typescript\n * interface GameComponents extends TransformComponentTypes {\n * sprite: Sprite;\n * velocity: { x: number; y: number };\n * }\n * ```\n */\nexport interface TransformComponentTypes {\n\tlocalTransform: LocalTransform;\n\tworldTransform: WorldTransform;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the transform bundle.\n */\nexport interface TransformBundleOptions {\n\t/** System group name (default: 'transform') */\n\tsystemGroup?: string;\n\t/** Priority for transform propagation (default: 500, runs after physics/movement) */\n\tpriority?: number;\n\t/** Execution phase (default: 'postUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Default Values ====================\n\n/**\n * Default local transform values.\n */\nexport const DEFAULT_LOCAL_TRANSFORM: Readonly<LocalTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n};\n\n/**\n * Default world transform values.\n */\nexport const DEFAULT_WORLD_TRANSFORM: Readonly<WorldTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n};\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a local transform component with position only.\n * Uses default rotation (0) and scale (1, 1).\n *\n * @param x The x coordinate\n * @param y The y coordinate\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createLocalTransform(100, 200),\n * sprite,\n * });\n * ```\n */\nexport function createLocalTransform(x: number, y: number): Pick<TransformComponentTypes, 'localTransform'> {\n\treturn {\n\t\tlocalTransform: {\n\t\t\tx,\n\t\t\ty,\n\t\t\trotation: 0,\n\t\t\tscaleX: 1,\n\t\t\tscaleY: 1,\n\t\t},\n\t};\n}\n\n/**\n * Create a world transform component with position only.\n * Typically used alongside createLocalTransform for initial state.\n *\n * @param x The x coordinate\n * @param y The y coordinate\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createWorldTransform(x: number, y: number): Pick<TransformComponentTypes, 'worldTransform'> {\n\treturn {\n\t\tworldTransform: {\n\t\t\tx,\n\t\t\ty,\n\t\t\trotation: 0,\n\t\t\tscaleX: 1,\n\t\t\tscaleY: 1,\n\t\t},\n\t};\n}\n\n/**\n * Options for creating a full transform.\n */\nexport interface TransformOptions {\n\trotation?: number;\n\tscaleX?: number;\n\tscaleY?: number;\n\t/** Uniform scale (overrides scaleX/scaleY if provided) */\n\tscale?: number;\n}\n\n/**\n * Create both local and world transform components.\n * World transform is initialized to match local transform.\n *\n * @param x The x coordinate\n * @param y The y coordinate\n * @param options Optional rotation and scale\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * sprite,\n * });\n *\n * // With rotation and scale\n * ecs.spawn({\n * ...createTransform(100, 200, { rotation: Math.PI / 4, scale: 2 }),\n * sprite,\n * });\n * ```\n */\nexport function createTransform(\n\tx: number,\n\ty: number,\n\toptions?: TransformOptions\n): TransformComponentTypes {\n\tconst scaleX = options?.scale ?? options?.scaleX ?? 1;\n\tconst scaleY = options?.scale ?? options?.scaleY ?? 1;\n\tconst rotation = options?.rotation ?? 0;\n\n\tconst transform = {\n\t\tx,\n\t\ty,\n\t\trotation,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n\n\treturn {\n\t\tlocalTransform: { ...transform },\n\t\tworldTransform: { ...transform },\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a transform bundle for ECSpresso.\n *\n * This bundle provides:\n * - Transform propagation system that computes world transforms from local transforms\n * - Parent-first traversal ensures parents are processed before children\n * - Supports full transform hierarchy (position, rotation, scale)\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso\n * .create<Components, Events, Resources>()\n * .withBundle(createTransformBundle())\n * .withBundle(createMovementBundle())\n * .build();\n *\n * // Spawn entity with transform\n * ecs.spawn({\n * ...createTransform(100, 200),\n * velocity: { x: 50, y: 0 },\n * });\n * ```\n */\nexport function createTransformBundle(\n\toptions?: TransformBundleOptions\n): Bundle<TransformComponentTypes, {}, {}> {\n\tconst {\n\t\tsystemGroup = 'transform',\n\t\tpriority = 500,\n\t\tphase = 'postUpdate',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<TransformComponentTypes, {}, {}>('transform');\n\n\tbundle\n\t\t.addSystem('transform-propagation')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.setProcess((_queries, _deltaTime, ecs) => {\n\t\t\tpropagateTransforms(ecs as ECSpresso<TransformComponentTypes, {}, {}>);\n\t\t})\n\t\t.and();\n\n\treturn bundle;\n}\n\n/**\n * Propagate transforms through the hierarchy.\n * Parent-first traversal ensures parents are computed before children.\n *\n * Only recomputes entities whose localTransform changed since this system\n * last ran, or whose parent's worldTransform changed (cascade).\n * Uses per-system monotonic sequence threshold for change detection.\n */\nfunction propagateTransforms(ecs: ECSpresso<TransformComponentTypes, {}, {}>): void {\n\tconst threshold = ecs.changeThreshold;\n\tconst em = ecs.entityManager;\n\n\t// Use parent-first traversal for entities in hierarchy\n\tecs.forEachInHierarchy((entityId, parentId) => {\n\t\tconst localTransform = em.getComponent(entityId, 'localTransform');\n\t\tconst worldTransform = em.getComponent(entityId, 'worldTransform');\n\n\t\tif (!localTransform || !worldTransform) return;\n\n\t\tconst localChanged = em.getChangeSeq(entityId, 'localTransform') > threshold;\n\t\tconst parentWorldChanged = parentId !== null\n\t\t\t&& em.getChangeSeq(parentId, 'worldTransform') > threshold;\n\n\t\tif (!localChanged && !parentWorldChanged) return;\n\n\t\tif (parentId === null) {\n\t\t\t// Root entity: world transform equals local transform\n\t\t\tcopyTransform(localTransform, worldTransform);\n\t\t} else {\n\t\t\t// Child entity: combine with parent's world transform\n\t\t\tconst parentWorld = em.getComponent(parentId, 'worldTransform');\n\t\t\tif (parentWorld) {\n\t\t\t\tcombineTransforms(parentWorld, localTransform, worldTransform);\n\t\t\t} else {\n\t\t\t\t// Parent has no world transform, treat as root\n\t\t\t\tcopyTransform(localTransform, worldTransform);\n\t\t\t}\n\t\t}\n\n\t\tecs.markChanged(entityId, 'worldTransform');\n\t});\n\n\t// Process orphaned entities (not in hierarchy but have transforms)\n\tconst orphanedEntities = ecs.getEntitiesWithQuery(['localTransform', 'worldTransform']);\n\tfor (const entity of orphanedEntities) {\n\t\tconst parentId = ecs.getParent(entity.id);\n\t\t// Only process if truly orphaned (no parent and not a root with children)\n\t\tif (parentId === null && ecs.getChildren(entity.id).length === 0) {\n\t\t\tconst localChanged = em.getChangeSeq(entity.id, 'localTransform') > threshold;\n\t\t\tif (!localChanged) continue;\n\n\t\t\tconst { localTransform, worldTransform } = entity.components;\n\t\t\tcopyTransform(localTransform, worldTransform);\n\t\t\tecs.markChanged(entity.id, 'worldTransform');\n\t\t}\n\t}\n}\n\n/**\n * Copy transform values from source to destination.\n */\nfunction copyTransform(src: LocalTransform, dest: WorldTransform): void {\n\tdest.x = src.x;\n\tdest.y = src.y;\n\tdest.rotation = src.rotation;\n\tdest.scaleX = src.scaleX;\n\tdest.scaleY = src.scaleY;\n}\n\n/**\n * Combine parent world transform with child local transform into child world transform.\n */\nfunction combineTransforms(\n\tparent: WorldTransform,\n\tlocal: LocalTransform,\n\tworld: WorldTransform\n): void {\n\t// Apply parent's scale to local position\n\tconst scaledLocalX = local.x * parent.scaleX;\n\tconst scaledLocalY = local.y * parent.scaleY;\n\n\t// Rotate local position by parent's rotation\n\tconst cos = Math.cos(parent.rotation);\n\tconst sin = Math.sin(parent.rotation);\n\tconst rotatedX = scaledLocalX * cos - scaledLocalY * sin;\n\tconst rotatedY = scaledLocalX * sin + scaledLocalY * cos;\n\n\t// Add to parent's position\n\tworld.x = parent.x + rotatedX;\n\tworld.y = parent.y + rotatedY;\n\tworld.rotation = parent.rotation + local.rotation;\n\tworld.scaleX = parent.scaleX * local.scaleX;\n\tworld.scaleY = parent.scaleY * local.scaleY;\n}\n",
|
|
8
|
-
"/**\n * Bounds Bundle for ECSpresso\n *\n * Provides screen bounds enforcement for entities with transforms.\n * Reads worldTransform for position checking; modifies localTransform for corrections.\n * Supports destroy, clamp, and wrap behaviors.\n */\n\nimport Bundle from '../../bundle';\nimport type { SystemPhase } from '../../types';\nimport type { TransformComponentTypes } from './transform';\n\n// ==================== Component Types ====================\n\n/**\n * Component that marks an entity for destruction when outside bounds.\n */\nexport interface DestroyOutOfBounds {\n\t/** Extra padding beyond bounds before destruction (default: 0) */\n\tpadding?: number;\n}\n\n/**\n * Component that clamps an entity's position to stay within bounds.\n */\nexport interface ClampToBounds {\n\t/** Margin to shrink the valid area (default: 0) */\n\tmargin?: number;\n}\n\n/**\n * Component that wraps an entity's position to the opposite edge.\n */\nexport interface WrapAtBounds {\n\t/** Padding beyond bounds before wrapping (default: 0) */\n\tpadding?: number;\n}\n\n/**\n * Component types provided by the bounds bundle.\n * Extend your component types with this interface.\n *\n * @example\n * ```typescript\n * interface GameComponents extends TransformComponentTypes, BoundsComponentTypes {\n * sprite: Sprite;\n * }\n * ```\n */\nexport interface BoundsComponentTypes {\n\tdestroyOutOfBounds: DestroyOutOfBounds;\n\tclampToBounds: ClampToBounds;\n\twrapAtBounds: WrapAtBounds;\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Bounds rectangle definition.\n */\nexport interface BoundsRect {\n\t/** Left edge x coordinate (default: 0) */\n\tx?: number;\n\t/** Top edge y coordinate (default: 0) */\n\ty?: number;\n\t/** Width of the bounds area */\n\twidth: number;\n\t/** Height of the bounds area */\n\theight: number;\n}\n\n/**\n * Resource types provided by the bounds bundle.\n */\nexport interface BoundsResourceTypes {\n\tbounds: BoundsRect;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when an entity exits bounds.\n */\nexport interface EntityOutOfBoundsEvent {\n\t/** The entity that exited bounds */\n\tentityId: number;\n\t/** The edge the entity exited through */\n\texitEdge: 'top' | 'bottom' | 'left' | 'right';\n}\n\n/**\n * Event types provided by the bounds bundle.\n */\nexport interface BoundsEventTypes {\n\tentityOutOfBounds: EntityOutOfBoundsEvent;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the bounds bundle.\n */\nexport interface BoundsBundleOptions {\n\t/** System group name (default: 'physics') */\n\tsystemGroup?: string;\n\t/** Priority for bounds systems (default: 50) */\n\tpriority?: number;\n\t/** Resource key for bounds rectangle (default: 'bounds') */\n\tboundsResourceKey?: string;\n\t/** Whether to auto-remove entities when out of bounds (default: true) */\n\tautoRemove?: boolean;\n\t/** Execution phase (default: 'postUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a bounds rectangle resource.\n *\n * @param width The width of the bounds area\n * @param height The height of the bounds area\n * @param x The left edge x coordinate (default: 0)\n * @param y The top edge y coordinate (default: 0)\n * @returns Bounds rectangle suitable for use as a resource\n *\n * @example\n * ```typescript\n * ECSpresso.create()\n * .withResource('bounds', createBounds(800, 600))\n * .build();\n * ```\n */\nexport function createBounds(width: number, height: number, x?: number, y?: number): BoundsRect {\n\tconst bounds: BoundsRect = { width, height };\n\tif (x !== undefined) bounds.x = x;\n\tif (y !== undefined) bounds.y = y;\n\treturn bounds;\n}\n\n/**\n * Create a destroyOutOfBounds component.\n *\n * @param padding Extra padding beyond bounds before destruction\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createDestroyOutOfBounds(20),\n * });\n * ```\n */\nexport function createDestroyOutOfBounds(padding?: number): Pick<BoundsComponentTypes, 'destroyOutOfBounds'> {\n\treturn {\n\t\tdestroyOutOfBounds: padding !== undefined ? { padding } : {},\n\t};\n}\n\n/**\n * Create a clampToBounds component.\n *\n * @param margin Margin to shrink the valid area\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createClampToBounds(30),\n * });\n * ```\n */\nexport function createClampToBounds(margin?: number): Pick<BoundsComponentTypes, 'clampToBounds'> {\n\treturn {\n\t\tclampToBounds: margin !== undefined ? { margin } : {},\n\t};\n}\n\n/**\n * Create a wrapAtBounds component.\n *\n * @param padding Padding beyond bounds before wrapping\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createWrapAtBounds(10),\n * });\n * ```\n */\nexport function createWrapAtBounds(padding?: number): Pick<BoundsComponentTypes, 'wrapAtBounds'> {\n\treturn {\n\t\twrapAtBounds: padding !== undefined ? { padding } : {},\n\t};\n}\n\n// ==================== Internal Types ====================\n\ntype CombinedComponentTypes = BoundsComponentTypes & TransformComponentTypes;\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a bounds bundle for ECSpresso.\n *\n * This bundle provides:\n * - Destroy out of bounds system - removes entities that exit bounds\n * - Clamp to bounds system - constrains entities within bounds\n * - Wrap at bounds system - wraps entities to opposite edge\n *\n * Uses worldTransform for position checking (world-space) and modifies\n * localTransform for corrections. Works best with entities that don't\n * have parent transforms (orphan entities).\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso\n * .create<Components, Events, Resources>()\n * .withResource('bounds', createBounds(800, 600))\n * .withBundle(createTransformBundle())\n * .withBundle(createBoundsBundle())\n * .build();\n *\n * // Entity that gets destroyed when leaving screen\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createDestroyOutOfBounds(),\n * });\n * ```\n */\nexport function createBoundsBundle<ResourceTypes extends BoundsResourceTypes = BoundsResourceTypes>(\n\toptions?: BoundsBundleOptions\n): Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes> {\n\tconst {\n\t\tsystemGroup = 'physics',\n\t\tpriority = 50,\n\t\tboundsResourceKey = 'bounds',\n\t\tautoRemove = true,\n\t\tphase = 'postUpdate',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes>('bounds');\n\n\t// Destroy out of bounds system\n\tbundle\n\t\t.addSystem('bounds-destroy')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['worldTransform', 'destroyOutOfBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { worldTransform, destroyOutOfBounds } = entity.components;\n\t\t\t\tconst padding = destroyOutOfBounds.padding ?? 0;\n\n\t\t\t\tconst exitEdge = getExitEdge(worldTransform, minX, minY, maxX, maxY, padding);\n\t\t\t\tif (!exitEdge) continue;\n\n\t\t\t\tecs.eventBus.publish('entityOutOfBounds', {\n\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\texitEdge,\n\t\t\t\t});\n\n\t\t\t\tif (autoRemove) {\n\t\t\t\t\tecs.commands.removeEntity(entity.id);\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// Clamp to bounds system\n\tbundle\n\t\t.addSystem('bounds-clamp')\n\t\t.setPriority(priority - 1)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['localTransform', 'worldTransform', 'clampToBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { localTransform, worldTransform, clampToBounds } = entity.components;\n\t\t\t\tconst margin = clampToBounds.margin ?? 0;\n\n\t\t\t\tconst clampedMinX = minX + margin;\n\t\t\t\tconst clampedMinY = minY + margin;\n\t\t\t\tconst clampedMaxX = maxX - margin;\n\t\t\t\tconst clampedMaxY = maxY - margin;\n\n\t\t\t\t// Calculate world-space correction and apply to local transform\n\t\t\t\t// For entities without parents, this is equivalent to direct position clamping\n\t\t\t\tlet deltaX = 0;\n\t\t\t\tlet deltaY = 0;\n\n\t\t\t\tif (worldTransform.x < clampedMinX) deltaX = clampedMinX - worldTransform.x;\n\t\t\t\tif (worldTransform.x > clampedMaxX) deltaX = clampedMaxX - worldTransform.x;\n\t\t\t\tif (worldTransform.y < clampedMinY) deltaY = clampedMinY - worldTransform.y;\n\t\t\t\tif (worldTransform.y > clampedMaxY) deltaY = clampedMaxY - worldTransform.y;\n\n\t\t\t\tif (deltaX !== 0 || deltaY !== 0) {\n\t\t\t\t\tlocalTransform.x += deltaX;\n\t\t\t\t\tlocalTransform.y += deltaY;\n\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// Wrap at bounds system\n\tbundle\n\t\t.addSystem('bounds-wrap')\n\t\t.setPriority(priority - 2)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['localTransform', 'worldTransform', 'wrapAtBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { localTransform, worldTransform, wrapAtBounds } = entity.components;\n\t\t\t\tconst padding = wrapAtBounds.padding ?? 0;\n\n\t\t\t\tlet deltaX = 0;\n\t\t\t\tlet deltaY = 0;\n\t\t\t\tconst boundsWidth = maxX - minX;\n\t\t\t\tconst boundsHeight = maxY - minY;\n\n\t\t\t\t// Wrap horizontally\n\t\t\t\tif (worldTransform.x > maxX + padding) {\n\t\t\t\t\tdeltaX = -(boundsWidth + 2 * padding);\n\t\t\t\t} else if (worldTransform.x < minX - padding) {\n\t\t\t\t\tdeltaX = boundsWidth + 2 * padding;\n\t\t\t\t}\n\n\t\t\t\t// Wrap vertically\n\t\t\t\tif (worldTransform.y > maxY + padding) {\n\t\t\t\t\tdeltaY = -(boundsHeight + 2 * padding);\n\t\t\t\t} else if (worldTransform.y < minY - padding) {\n\t\t\t\t\tdeltaY = boundsHeight + 2 * padding;\n\t\t\t\t}\n\n\t\t\t\tif (deltaX !== 0 || deltaY !== 0) {\n\t\t\t\t\tlocalTransform.x += deltaX;\n\t\t\t\t\tlocalTransform.y += deltaY;\n\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\treturn bundle;\n}\n\n/**\n * Determine which edge an entity has exited through, if any.\n */\nfunction getExitEdge(\n\ttransform: { x: number; y: number },\n\tminX: number,\n\tminY: number,\n\tmaxX: number,\n\tmaxY: number,\n\tpadding: number\n): 'top' | 'bottom' | 'left' | 'right' | null {\n\tif (transform.x > maxX + padding) return 'right';\n\tif (transform.x < minX - padding) return 'left';\n\tif (transform.y > maxY + padding) return 'bottom';\n\tif (transform.y < minY - padding) return 'top';\n\treturn null;\n}\n",
|
|
9
|
-
"/**\n * 2D Renderer Bundle for ECSpresso\n *\n * An opt-in PixiJS-based 2D rendering bundle that automates scene graph wiring.\n * Import from 'ecspresso/bundles/renderers/renderer2D'\n *\n * This bundle includes transform propagation automatically.\n */\n\nimport type { Application, ApplicationOptions, Container, Sprite, Graphics } from 'pixi.js';\nimport Bundle, { mergeBundles } from '../../bundle';\nimport type ECSpresso from '../../ecspresso';\nimport {\n\tcreateTransformBundle,\n\ttype LocalTransform,\n\ttype WorldTransform,\n\ttype TransformComponentTypes,\n\ttype TransformBundleOptions,\n} from '../utils/transform';\nimport { createBounds, type BoundsRect } from '../utils/bounds';\n\n// Re-export transform and bounds types for convenience\nexport type { LocalTransform, WorldTransform, TransformComponentTypes };\nexport type { BoundsRect };\nexport { createTransform, createLocalTransform, createWorldTransform } from '../utils/transform';\n\n// Dynamic import for Application to avoid requiring pixi.js at bundle creation time\n// when using managed mode (init options instead of pre-initialized app)\nasync function createPixiApplication(options: Partial<ApplicationOptions>): Promise<Application> {\n\tconst { Application } = await import('pixi.js');\n\tconst app = new Application();\n\tawait app.init(options);\n\treturn app;\n}\n\n// ==================== Component Types ====================\n\n/**\n * Visibility and alpha component\n */\nexport interface Visible {\n\tvisible: boolean;\n\talpha?: number;\n}\n\n/**\n * Aggregate component types for the 2D renderer bundle.\n * Users should extend this interface with their own component types.\n *\n * @example\n * ```typescript\n * interface GameComponents extends Renderer2DComponentTypes {\n * velocity: { x: number; y: number };\n * player: true;\n * }\n * ```\n */\nexport interface Renderer2DComponentTypes extends TransformComponentTypes {\n\tsprite: Sprite;\n\tgraphics: Graphics;\n\tcontainer: Container;\n\tvisible: Visible;\n\t/** Assigns the entity to a named render layer for z-ordering */\n\trenderLayer: string;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Events emitted by the 2D renderer bundle\n */\nexport interface Renderer2DEventTypes {\n\thierarchyChanged: {\n\t\tentityId: number;\n\t\toldParent: number | null;\n\t\tnewParent: number | null;\n\t};\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Resources provided by the 2D renderer bundle\n */\nexport interface Renderer2DResourceTypes {\n\tpixiApp: Application;\n\trootContainer: Container;\n\t/** Screen bounds derived from PixiJS screen dimensions, updated on resize */\n\tbounds: BoundsRect;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Common options shared between both initialization modes\n */\ninterface Renderer2DBundleCommonOptions {\n\t/** Optional custom root container (defaults to app.stage) */\n\trootContainer?: Container;\n\t/** System group name (default: 'renderer2d') */\n\tsystemGroup?: string;\n\t/** Priority for render sync system (default: 500) */\n\trenderSyncPriority?: number;\n\t/** Options for the included transform bundle */\n\ttransform?: TransformBundleOptions;\n\t/** When true, wires up pixiApp.ticker to drive ecs.update() automatically (default: true) */\n\tstartLoop?: boolean;\n\t/** Ordered render layer names (back-to-front). Entities with a renderLayer component are placed in the corresponding container. */\n\trenderLayers?: string[];\n}\n\n/**\n * Options when providing a pre-initialized PixiJS Application\n */\nexport interface Renderer2DBundleAppOptions extends Renderer2DBundleCommonOptions {\n\t/** The PixiJS Application instance (already initialized) */\n\tapp: Application;\n\tinit?: never;\n\tcontainer?: never;\n}\n\n/**\n * Options when letting the bundle create and manage the PixiJS Application\n */\nexport interface Renderer2DBundleManagedOptions extends Renderer2DBundleCommonOptions {\n\tapp?: never;\n\t/** PixiJS ApplicationOptions - bundle will create and initialize the Application */\n\tinit: Partial<ApplicationOptions>;\n\t/** Container element to append the canvas to, or CSS selector string */\n\tcontainer?: HTMLElement | string;\n}\n\n/**\n * Configuration options for the 2D renderer bundle.\n *\n * Supports two modes:\n * 1. **Pre-initialized**: Pass an already-initialized Application via `app`\n * 2. **Managed**: Pass `init` options and the bundle creates the Application during `ecs.initialize()`\n *\n * This bundle includes transform propagation automatically - no need to add createTransformBundle() separately.\n *\n * @example Pre-initialized mode (full control)\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createRenderer2DBundle({ app }))\n * .build();\n * ```\n *\n * @example Managed mode (convenience)\n * ```typescript\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createRenderer2DBundle({\n * init: { background: '#1099bb', resizeTo: window },\n * container: document.body,\n * }))\n * .build();\n * await ecs.initialize(); // Application created here\n * ```\n */\nexport type Renderer2DBundleOptions = Renderer2DBundleAppOptions | Renderer2DBundleManagedOptions;\n\n// ==================== Default Values ====================\n\n/**\n * Default local transform values\n */\nexport const DEFAULT_LOCAL_TRANSFORM: Readonly<LocalTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n};\n\n/**\n * Default world transform values\n */\nexport const DEFAULT_WORLD_TRANSFORM: Readonly<WorldTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n};\n\n// ==================== Helper Utilities ====================\n\ninterface PositionOption {\n\tx?: number;\n\ty?: number;\n}\n\ninterface TransformOptions {\n\trotation?: number;\n\tscale?: number | { x: number; y: number };\n\tvisible?: boolean;\n\talpha?: number;\n}\n\nfunction createLocalTransformInternal(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): LocalTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createWorldTransformInternal(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): WorldTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createVisibleComponent(options?: TransformOptions): Visible {\n\treturn {\n\t\tvisible: options?.visible ?? true,\n\t\talpha: options?.alpha,\n\t};\n}\n\n/**\n * Create components for a sprite entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const player = ecs.spawn({\n * ...createSpriteComponents(new Sprite(texture), { x: 100, y: 100 }),\n * velocity: { x: 0, y: 0 },\n * });\n * ```\n */\nexport function createSpriteComponents(\n\tsprite: Sprite,\n\tposition?: PositionOption,\n\toptions?: TransformOptions & { anchor?: { x: number; y: number } }\n): Pick<Renderer2DComponentTypes, 'sprite' | 'localTransform' | 'worldTransform' | 'visible'> {\n\tif (options?.anchor) {\n\t\tsprite.anchor.set(options.anchor.x, options.anchor.y);\n\t}\n\treturn {\n\t\tsprite,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a graphics entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const rect = ecs.spawn({\n * ...createGraphicsComponents(graphics, { x: 50, y: 50 }),\n * });\n * ```\n */\nexport function createGraphicsComponents(\n\tgraphics: Graphics,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<Renderer2DComponentTypes, 'graphics' | 'localTransform' | 'worldTransform' | 'visible'> {\n\treturn {\n\t\tgraphics,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a container entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const group = ecs.spawn({\n * ...createContainerComponents(new Container(), { x: 0, y: 0 }),\n * });\n * ```\n */\nexport function createContainerComponents(\n\tcontainer: Container,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<Renderer2DComponentTypes, 'container' | 'localTransform' | 'worldTransform' | 'visible'> {\n\treturn {\n\t\tcontainer,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a 2D rendering bundle for ECSpresso.\n *\n * This bundle provides:\n * - Transform propagation (localTransform -> worldTransform)\n * - Render sync system (updates PixiJS objects from ECS components)\n * - Scene graph management (mirrors ECS hierarchy in PixiJS scene graph)\n *\n * @example Pre-initialized mode\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n *\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createRenderer2DBundle({ app }))\n * .build();\n * ```\n *\n * @example Managed mode\n * ```typescript\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createRenderer2DBundle({\n * init: { background: '#1099bb', resizeTo: window },\n * container: document.body,\n * }))\n * .build();\n * await ecs.initialize();\n * ```\n */\nexport function createRenderer2DBundle(\n\toptions: Renderer2DBundleOptions\n): Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes> {\n\tconst {\n\t\trootContainer: customRootContainer,\n\t\tsystemGroup = 'renderer2d',\n\t\trenderSyncPriority = 500,\n\t\ttransform: transformOptions,\n\t\tstartLoop = true,\n\t\trenderLayers = [],\n\t} = options;\n\n\tconst rendererBundle = new Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>('renderer2d-internal');\n\n\t// Determine mode and set up resources accordingly\n\tconst isManaged = 'init' in options && options.init !== undefined;\n\n\tif (isManaged) {\n\t\t// Managed mode: create Application during initialization\n\t\tconst initOptions = options.init;\n\t\tconst containerOption = options.container;\n\n\t\t// Resource factory that creates the Application\n\t\trendererBundle.addResource('pixiApp', async () => {\n\t\t\tconst app = await createPixiApplication(initOptions);\n\n\t\t\t// Auto-append canvas if container specified\n\t\t\tif (containerOption) {\n\t\t\t\tconst containerEl = typeof containerOption === 'string'\n\t\t\t\t\t? document.querySelector(containerOption)\n\t\t\t\t\t: containerOption;\n\n\t\t\t\tif (containerEl) {\n\t\t\t\t\tcontainerEl.appendChild(app.canvas);\n\t\t\t\t} else if (typeof containerOption === 'string') {\n\t\t\t\t\tconsole.warn(`Renderer2D bundle: container selector \"${containerOption}\" not found`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn app;\n\t\t});\n\n\t\t// rootContainer depends on pixiApp - declarative dependency\n\t\trendererBundle.addResource('rootContainer', {\n\t\t\tdependsOn: ['pixiApp'],\n\t\t\tfactory: (ecs) => customRootContainer ?? ecs.getResource('pixiApp').stage,\n\t\t});\n\n\t\t// Bounds resource derived from screen dimensions\n\t\trendererBundle.addResource('bounds', {\n\t\t\tdependsOn: ['pixiApp'],\n\t\t\tfactory: (ecs) => {\n\t\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\t\treturn createBounds(pixiApp.screen.width, pixiApp.screen.height);\n\t\t\t},\n\t\t});\n\t} else {\n\t\t// Pre-initialized mode: use provided Application\n\t\tconst app = options.app;\n\t\trendererBundle.addResource('pixiApp', app);\n\t\trendererBundle.addResource('rootContainer', customRootContainer ?? app.stage);\n\t\trendererBundle.addResource('bounds', createBounds(app.screen.width, app.screen.height));\n\t}\n\n\t// Entity ID -> PixiJS Container mapping for scene graph management\n\tconst entityToPixiObject = new Map<number, Container>();\n\n\t// Render layer name -> PixiJS Container mapping\n\tconst layerContainers = new Map<string, Container>();\n\n\t// Container constructor captured during initialization via dynamic import\n\t// Used by getOrCreateLayerContainer for lazy layer creation\n\tlet createLayerContainer: (label: string) => Container;\n\n\t// Helper to get the PixiJS display object for an entity\n\tfunction getPixiObject(entityId: number, ecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>): Container | null {\n\t\t// Check cache first\n\t\tconst cached = entityToPixiObject.get(entityId);\n\t\tif (cached) return cached;\n\n\t\t// Try to get from components\n\t\tconst spriteComp = ecs.entityManager.getComponent(entityId, 'sprite');\n\t\tif (spriteComp) {\n\t\t\tentityToPixiObject.set(entityId, spriteComp);\n\t\t\treturn spriteComp;\n\t\t}\n\n\t\tconst graphicsComp = ecs.entityManager.getComponent(entityId, 'graphics');\n\t\tif (graphicsComp) {\n\t\t\tentityToPixiObject.set(entityId, graphicsComp);\n\t\t\treturn graphicsComp;\n\t\t}\n\n\t\tconst containerComp = ecs.entityManager.getComponent(entityId, 'container');\n\t\tif (containerComp) {\n\t\t\tentityToPixiObject.set(entityId, containerComp);\n\t\t\treturn containerComp;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// Helper to get or create a render layer container\n\tfunction getOrCreateLayerContainer(\n\t\tlayerName: string,\n\t\trootCont: Container\n\t): Container {\n\t\tconst existing = layerContainers.get(layerName);\n\t\tif (existing) return existing;\n\n\t\t// Lazy-create for undeclared layers, appended to end\n\t\tconst cont = createLayerContainer(`layer:${layerName}`);\n\t\tlayerContainers.set(layerName, cont);\n\t\trootCont.addChild(cont);\n\t\treturn cont;\n\t}\n\n\t// Helper to resolve the target container for an entity\n\tfunction resolveTargetContainer(\n\t\tentityId: number,\n\t\tecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>\n\t): Container {\n\t\tconst rootCont = ecs.getResource('rootContainer');\n\n\t\t// 1. Check ECS parent hierarchy\n\t\tconst parentId = ecs.getParent(entityId);\n\t\tconst parentPixiObject = parentId !== null ? getPixiObject(parentId, ecs) : null;\n\t\tif (parentPixiObject) return parentPixiObject;\n\n\t\t// 2. Check render layer component\n\t\tconst layerName = ecs.entityManager.getComponent(entityId, 'renderLayer');\n\t\tif (layerName) return getOrCreateLayerContainer(layerName, rootCont);\n\n\t\t// 3. Fall back to root container\n\t\treturn rootCont;\n\t}\n\n\t// Helper to add a PixiJS object to the scene graph\n\tfunction addToSceneGraph(\n\t\tentityId: number,\n\t\tpixiObject: Container,\n\t\tecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>\n\t): void {\n\t\tconst targetContainer = resolveTargetContainer(entityId, ecs);\n\n\t\t// Only add if not already a child\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// Helper to remove a PixiJS object from scene graph\n\tfunction removeFromSceneGraph(entityId: number): void {\n\t\tconst pixiObject = entityToPixiObject.get(entityId);\n\t\tif (pixiObject) {\n\t\t\tpixiObject.removeFromParent();\n\t\t\tentityToPixiObject.delete(entityId);\n\t\t}\n\t}\n\n\t// Helper to update parent in scene graph\n\tfunction updateSceneGraphParent(\n\t\tentityId: number,\n\t\tecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>\n\t): void {\n\t\tconst pixiObject = entityToPixiObject.get(entityId);\n\t\tif (!pixiObject) return;\n\n\t\tconst targetContainer = resolveTargetContainer(entityId, ecs);\n\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\tpixiObject.removeFromParent();\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// ==================== Render Sync System ====================\n\t// Updates PixiJS objects from world transforms and visibility\n\trendererBundle\n\t\t.addSystem('renderer2d-sync')\n\t\t.setPriority(renderSyncPriority)\n\t\t.inPhase('render')\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('sprites', {\n\t\t\twith: ['sprite', 'worldTransform'],\n\t\t\tchanged: ['worldTransform'],\n\t\t})\n\t\t.addQuery('graphics', {\n\t\t\twith: ['graphics', 'worldTransform'],\n\t\t\tchanged: ['worldTransform'],\n\t\t})\n\t\t.addQuery('containers', {\n\t\t\twith: ['container', 'worldTransform'],\n\t\t\tchanged: ['worldTransform'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\t// Process sprites\n\t\t\tfor (const entity of queries.sprites) {\n\t\t\t\tconst { sprite, worldTransform } = entity.components;\n\n\t\t\t\tsprite.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tsprite.rotation = worldTransform.rotation;\n\t\t\t\tsprite.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visibleComp = ecs.entityManager.getComponent(entity.id, 'visible');\n\t\t\t\tif (visibleComp) {\n\t\t\t\t\tsprite.visible = visibleComp.visible;\n\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\tsprite.alpha = visibleComp.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Process graphics\n\t\t\tfor (const entity of queries.graphics) {\n\t\t\t\tconst { graphics, worldTransform } = entity.components;\n\n\t\t\t\tgraphics.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tgraphics.rotation = worldTransform.rotation;\n\t\t\t\tgraphics.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visibleComp = ecs.entityManager.getComponent(entity.id, 'visible');\n\t\t\t\tif (visibleComp) {\n\t\t\t\t\tgraphics.visible = visibleComp.visible;\n\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\tgraphics.alpha = visibleComp.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Process containers\n\t\t\tfor (const entity of queries.containers) {\n\t\t\t\tconst { container, worldTransform } = entity.components;\n\n\t\t\t\tcontainer.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tcontainer.rotation = worldTransform.rotation;\n\t\t\t\tcontainer.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visibleComp = ecs.entityManager.getComponent(entity.id, 'visible');\n\t\t\t\tif (visibleComp) {\n\t\t\t\t\tcontainer.visible = visibleComp.visible;\n\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\tcontainer.alpha = visibleComp.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// ==================== Scene Graph Manager System ====================\n\t// Sets up reactive queries to manage scene graph on entity create/destroy\n\t// High priority ensures this runs before user systems' onInitialize\n\trendererBundle\n\t\t.addSystem('renderer2d-scene-graph')\n\t\t.setPriority(9999)\n\t\t.inGroup(systemGroup)\n\t\t.setOnInitialize(async (ecs) => {\n\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\tconst rootCont = ecs.getResource('rootContainer');\n\n\t\t\t// Capture Container constructor via dynamic import (same module instance as pixi.js internals)\n\t\t\tconst { Container: ContainerClass } = await import('pixi.js');\n\t\t\tcreateLayerContainer = (label: string) => {\n\t\t\t\tconst cont = new ContainerClass();\n\t\t\t\tcont.label = label;\n\t\t\t\treturn cont;\n\t\t\t};\n\n\t\t\t// Create declared render layer containers in order (back-to-front)\n\t\t\tfor (const layerName of renderLayers) {\n\t\t\t\tconst cont = createLayerContainer(`layer:${layerName}`);\n\t\t\t\tlayerContainers.set(layerName, cont);\n\t\t\t\trootCont.addChild(cont);\n\t\t\t}\n\n\t\t\t// Reactive query for sprites\n\t\t\tecs.addReactiveQuery('renderer2d-sprites', {\n\t\t\t\twith: ['sprite'],\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.sprite;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tremoveFromSceneGraph(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Reactive query for graphics\n\t\t\tecs.addReactiveQuery('renderer2d-graphics', {\n\t\t\t\twith: ['graphics'],\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.graphics;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tremoveFromSceneGraph(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Reactive query for containers\n\t\t\tecs.addReactiveQuery('renderer2d-containers', {\n\t\t\t\twith: ['container'],\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.container;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tremoveFromSceneGraph(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Subscribe to hierarchy changes to mirror reparenting in scene graph\n\t\t\tecs.on('hierarchyChanged', ({ entityId }) => {\n\t\t\t\tupdateSceneGraphParent(entityId, ecs);\n\t\t\t});\n\n\t\t\t// Re-parent entity when render layer is added or changed\n\t\t\tecs.onComponentAdded('renderLayer', (_layerName, entity) => {\n\t\t\t\tupdateSceneGraphParent(entity.id, ecs);\n\t\t\t});\n\n\t\t\t// Re-parent entity when render layer is removed\n\t\t\tecs.onComponentRemoved('renderLayer', (_oldLayerName, entity) => {\n\t\t\t\tupdateSceneGraphParent(entity.id, ecs);\n\t\t\t});\n\n\t\t\t// Track screen bounds on resize\n\t\t\tpixiApp.renderer.on('resize', (width: number, height: number) => {\n\t\t\t\tconst bounds = ecs.getResource('bounds');\n\t\t\t\tbounds.width = width;\n\t\t\t\tbounds.height = height;\n\t\t\t});\n\n\t\t\t// Wire up the game loop if requested\n\t\t\tif (startLoop) {\n\t\t\t\tpixiApp.ticker.add((ticker) => {\n\t\t\t\t\tecs.update(ticker.deltaMS / 1_000);\n\t\t\t\t});\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// Merge transform bundle (runs first) with renderer bundle\n\tconst transformBundle = createTransformBundle(transformOptions);\n\treturn mergeBundles('renderer2d', transformBundle, rendererBundle);\n}\n"
|
|
5
|
+
"/**\n * 2D Renderer Bundle for ECSpresso\n *\n * An opt-in PixiJS-based 2D rendering bundle that automates scene graph wiring.\n * Import from 'ecspresso/bundles/renderers/renderer2D'\n *\n * This bundle includes transform propagation automatically.\n */\n\nimport type { Application, ApplicationOptions, Container, Sprite, Graphics } from 'pixi.js';\nimport { Bundle, mergeBundles } from 'ecspresso';\nimport type ECSpresso from 'ecspresso';\nimport {\n\tcreateTransformBundle,\n\ttype LocalTransform,\n\ttype WorldTransform,\n\ttype TransformComponentTypes,\n\ttype TransformBundleOptions,\n} from 'ecspresso/bundles/utils/transform';\nimport { createBounds, type BoundsRect } from 'ecspresso/bundles/utils/bounds';\n\n// Re-export transform and bounds types for convenience\nexport type { LocalTransform, WorldTransform, TransformComponentTypes };\nexport type { BoundsRect };\nexport { createTransform, createLocalTransform, createWorldTransform } from 'ecspresso/bundles/utils/transform';\n\n// Dynamic import for Application to avoid requiring pixi.js at bundle creation time\n// when using managed mode (init options instead of pre-initialized app)\nasync function createPixiApplication(options: Partial<ApplicationOptions>): Promise<Application> {\n\tconst { Application } = await import('pixi.js');\n\tconst app = new Application();\n\tawait app.init(options);\n\treturn app;\n}\n\n// ==================== Component Types ====================\n\n/**\n * Visibility and alpha component\n */\nexport interface Visible {\n\tvisible: boolean;\n\talpha?: number;\n}\n\n/**\n * Aggregate component types for the 2D renderer bundle.\n * Users should extend this interface with their own component types.\n *\n * @example\n * ```typescript\n * interface GameComponents extends Renderer2DComponentTypes {\n * velocity: { x: number; y: number };\n * player: true;\n * }\n * ```\n */\nexport interface Renderer2DComponentTypes extends TransformComponentTypes {\n\tsprite: Sprite;\n\tgraphics: Graphics;\n\tcontainer: Container;\n\tvisible: Visible;\n\t/** Assigns the entity to a named render layer for z-ordering */\n\trenderLayer: string;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Events emitted by the 2D renderer bundle\n */\nexport interface Renderer2DEventTypes {\n\thierarchyChanged: {\n\t\tentityId: number;\n\t\toldParent: number | null;\n\t\tnewParent: number | null;\n\t};\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Resources provided by the 2D renderer bundle\n */\nexport interface Renderer2DResourceTypes {\n\tpixiApp: Application;\n\trootContainer: Container;\n\t/** Screen bounds derived from PixiJS screen dimensions, updated on resize */\n\tbounds: BoundsRect;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Common options shared between both initialization modes\n */\ninterface Renderer2DBundleCommonOptions {\n\t/** Optional custom root container (defaults to app.stage) */\n\trootContainer?: Container;\n\t/** System group name (default: 'renderer2d') */\n\tsystemGroup?: string;\n\t/** Priority for render sync system (default: 500) */\n\trenderSyncPriority?: number;\n\t/** Options for the included transform bundle */\n\ttransform?: TransformBundleOptions;\n\t/** When true, wires up pixiApp.ticker to drive ecs.update() automatically (default: true) */\n\tstartLoop?: boolean;\n\t/** Ordered render layer names (back-to-front). Entities with a renderLayer component are placed in the corresponding container. */\n\trenderLayers?: string[];\n}\n\n/**\n * Options when providing a pre-initialized PixiJS Application\n */\nexport interface Renderer2DBundleAppOptions extends Renderer2DBundleCommonOptions {\n\t/** The PixiJS Application instance (already initialized) */\n\tapp: Application;\n\tinit?: never;\n\tcontainer?: never;\n}\n\n/**\n * Options when letting the bundle create and manage the PixiJS Application\n */\nexport interface Renderer2DBundleManagedOptions extends Renderer2DBundleCommonOptions {\n\tapp?: never;\n\t/** PixiJS ApplicationOptions - bundle will create and initialize the Application */\n\tinit: Partial<ApplicationOptions>;\n\t/** Container element to append the canvas to, or CSS selector string */\n\tcontainer?: HTMLElement | string;\n}\n\n/**\n * Configuration options for the 2D renderer bundle.\n *\n * Supports two modes:\n * 1. **Pre-initialized**: Pass an already-initialized Application via `app`\n * 2. **Managed**: Pass `init` options and the bundle creates the Application during `ecs.initialize()`\n *\n * This bundle includes transform propagation automatically - no need to add createTransformBundle() separately.\n *\n * @example Pre-initialized mode (full control)\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createRenderer2DBundle({ app }))\n * .build();\n * ```\n *\n * @example Managed mode (convenience)\n * ```typescript\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createRenderer2DBundle({\n * init: { background: '#1099bb', resizeTo: window },\n * container: document.body,\n * }))\n * .build();\n * await ecs.initialize(); // Application created here\n * ```\n */\nexport type Renderer2DBundleOptions = Renderer2DBundleAppOptions | Renderer2DBundleManagedOptions;\n\n// ==================== Default Values ====================\n\n/**\n * Default local transform values\n */\nexport const DEFAULT_LOCAL_TRANSFORM: Readonly<LocalTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n};\n\n/**\n * Default world transform values\n */\nexport const DEFAULT_WORLD_TRANSFORM: Readonly<WorldTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n};\n\n// ==================== Helper Utilities ====================\n\ninterface PositionOption {\n\tx?: number;\n\ty?: number;\n}\n\ninterface TransformOptions {\n\trotation?: number;\n\tscale?: number | { x: number; y: number };\n\tvisible?: boolean;\n\talpha?: number;\n}\n\nfunction createLocalTransformInternal(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): LocalTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createWorldTransformInternal(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): WorldTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createVisibleComponent(options?: TransformOptions): Visible {\n\treturn {\n\t\tvisible: options?.visible ?? true,\n\t\talpha: options?.alpha,\n\t};\n}\n\n/**\n * Create components for a sprite entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const player = ecs.spawn({\n * ...createSpriteComponents(new Sprite(texture), { x: 100, y: 100 }),\n * velocity: { x: 0, y: 0 },\n * });\n * ```\n */\nexport function createSpriteComponents(\n\tsprite: Sprite,\n\tposition?: PositionOption,\n\toptions?: TransformOptions & { anchor?: { x: number; y: number } }\n): Pick<Renderer2DComponentTypes, 'sprite' | 'localTransform' | 'worldTransform' | 'visible'> {\n\tif (options?.anchor) {\n\t\tsprite.anchor.set(options.anchor.x, options.anchor.y);\n\t}\n\treturn {\n\t\tsprite,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a graphics entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const rect = ecs.spawn({\n * ...createGraphicsComponents(graphics, { x: 50, y: 50 }),\n * });\n * ```\n */\nexport function createGraphicsComponents(\n\tgraphics: Graphics,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<Renderer2DComponentTypes, 'graphics' | 'localTransform' | 'worldTransform' | 'visible'> {\n\treturn {\n\t\tgraphics,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a container entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const group = ecs.spawn({\n * ...createContainerComponents(new Container(), { x: 0, y: 0 }),\n * });\n * ```\n */\nexport function createContainerComponents(\n\tcontainer: Container,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<Renderer2DComponentTypes, 'container' | 'localTransform' | 'worldTransform' | 'visible'> {\n\treturn {\n\t\tcontainer,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a 2D rendering bundle for ECSpresso.\n *\n * This bundle provides:\n * - Transform propagation (localTransform -> worldTransform)\n * - Render sync system (updates PixiJS objects from ECS components)\n * - Scene graph management (mirrors ECS hierarchy in PixiJS scene graph)\n *\n * @example Pre-initialized mode\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n *\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createRenderer2DBundle({ app }))\n * .build();\n * ```\n *\n * @example Managed mode\n * ```typescript\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createRenderer2DBundle({\n * init: { background: '#1099bb', resizeTo: window },\n * container: document.body,\n * }))\n * .build();\n * await ecs.initialize();\n * ```\n */\nexport function createRenderer2DBundle(\n\toptions: Renderer2DBundleOptions\n): Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes> {\n\tconst {\n\t\trootContainer: customRootContainer,\n\t\tsystemGroup = 'renderer2d',\n\t\trenderSyncPriority = 500,\n\t\ttransform: transformOptions,\n\t\tstartLoop = true,\n\t\trenderLayers = [],\n\t} = options;\n\n\tconst rendererBundle = new Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>('renderer2d-internal');\n\n\t// Determine mode and set up resources accordingly\n\tconst isManaged = 'init' in options && options.init !== undefined;\n\n\tif (isManaged) {\n\t\t// Managed mode: create Application during initialization\n\t\tconst initOptions = options.init;\n\t\tconst containerOption = options.container;\n\n\t\t// Resource factory that creates the Application\n\t\trendererBundle.addResource('pixiApp', async () => {\n\t\t\tconst app = await createPixiApplication(initOptions);\n\n\t\t\t// Auto-append canvas if container specified\n\t\t\tif (containerOption) {\n\t\t\t\tconst containerEl = typeof containerOption === 'string'\n\t\t\t\t\t? document.querySelector(containerOption)\n\t\t\t\t\t: containerOption;\n\n\t\t\t\tif (containerEl) {\n\t\t\t\t\tcontainerEl.appendChild(app.canvas);\n\t\t\t\t} else if (typeof containerOption === 'string') {\n\t\t\t\t\tconsole.warn(`Renderer2D bundle: container selector \"${containerOption}\" not found`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn app;\n\t\t});\n\n\t\t// rootContainer depends on pixiApp - declarative dependency\n\t\trendererBundle.addResource('rootContainer', {\n\t\t\tdependsOn: ['pixiApp'],\n\t\t\tfactory: (ecs) => customRootContainer ?? ecs.getResource('pixiApp').stage,\n\t\t});\n\n\t\t// Bounds resource derived from screen dimensions\n\t\trendererBundle.addResource('bounds', {\n\t\t\tdependsOn: ['pixiApp'],\n\t\t\tfactory: (ecs) => {\n\t\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\t\treturn createBounds(pixiApp.screen.width, pixiApp.screen.height);\n\t\t\t},\n\t\t});\n\t} else {\n\t\t// Pre-initialized mode: use provided Application\n\t\tconst app = options.app;\n\t\trendererBundle.addResource('pixiApp', app);\n\t\trendererBundle.addResource('rootContainer', customRootContainer ?? app.stage);\n\t\trendererBundle.addResource('bounds', createBounds(app.screen.width, app.screen.height));\n\t}\n\n\t// Entity ID -> PixiJS Container mapping for scene graph management\n\tconst entityToPixiObject = new Map<number, Container>();\n\n\t// Render layer name -> PixiJS Container mapping\n\tconst layerContainers = new Map<string, Container>();\n\n\t// Container constructor captured during initialization via dynamic import\n\t// Used by getOrCreateLayerContainer for lazy layer creation\n\tlet createLayerContainer: (label: string) => Container;\n\n\t// Helper to get the PixiJS display object for an entity\n\tfunction getPixiObject(entityId: number, ecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>): Container | null {\n\t\t// Check cache first\n\t\tconst cached = entityToPixiObject.get(entityId);\n\t\tif (cached) return cached;\n\n\t\t// Try to get from components\n\t\tconst spriteComp = ecs.entityManager.getComponent(entityId, 'sprite');\n\t\tif (spriteComp) {\n\t\t\tentityToPixiObject.set(entityId, spriteComp);\n\t\t\treturn spriteComp;\n\t\t}\n\n\t\tconst graphicsComp = ecs.entityManager.getComponent(entityId, 'graphics');\n\t\tif (graphicsComp) {\n\t\t\tentityToPixiObject.set(entityId, graphicsComp);\n\t\t\treturn graphicsComp;\n\t\t}\n\n\t\tconst containerComp = ecs.entityManager.getComponent(entityId, 'container');\n\t\tif (containerComp) {\n\t\t\tentityToPixiObject.set(entityId, containerComp);\n\t\t\treturn containerComp;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// Helper to get or create a render layer container\n\tfunction getOrCreateLayerContainer(\n\t\tlayerName: string,\n\t\trootCont: Container\n\t): Container {\n\t\tconst existing = layerContainers.get(layerName);\n\t\tif (existing) return existing;\n\n\t\t// Lazy-create for undeclared layers, appended to end\n\t\tconst cont = createLayerContainer(`layer:${layerName}`);\n\t\tlayerContainers.set(layerName, cont);\n\t\trootCont.addChild(cont);\n\t\treturn cont;\n\t}\n\n\t// Helper to resolve the target container for an entity\n\tfunction resolveTargetContainer(\n\t\tentityId: number,\n\t\tecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>\n\t): Container {\n\t\tconst rootCont = ecs.getResource('rootContainer');\n\n\t\t// 1. Check ECS parent hierarchy\n\t\tconst parentId = ecs.getParent(entityId);\n\t\tconst parentPixiObject = parentId !== null ? getPixiObject(parentId, ecs) : null;\n\t\tif (parentPixiObject) return parentPixiObject;\n\n\t\t// 2. Check render layer component\n\t\tconst layerName = ecs.entityManager.getComponent(entityId, 'renderLayer');\n\t\tif (layerName) return getOrCreateLayerContainer(layerName, rootCont);\n\n\t\t// 3. Fall back to root container\n\t\treturn rootCont;\n\t}\n\n\t// Helper to add a PixiJS object to the scene graph\n\tfunction addToSceneGraph(\n\t\tentityId: number,\n\t\tpixiObject: Container,\n\t\tecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>\n\t): void {\n\t\tconst targetContainer = resolveTargetContainer(entityId, ecs);\n\n\t\t// Only add if not already a child\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// Helper to remove a PixiJS object from scene graph\n\tfunction removeFromSceneGraph(entityId: number): void {\n\t\tconst pixiObject = entityToPixiObject.get(entityId);\n\t\tif (pixiObject) {\n\t\t\tpixiObject.removeFromParent();\n\t\t\tentityToPixiObject.delete(entityId);\n\t\t}\n\t}\n\n\t// Helper to update parent in scene graph\n\tfunction updateSceneGraphParent(\n\t\tentityId: number,\n\t\tecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>\n\t): void {\n\t\tconst pixiObject = entityToPixiObject.get(entityId);\n\t\tif (!pixiObject) return;\n\n\t\tconst targetContainer = resolveTargetContainer(entityId, ecs);\n\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\tpixiObject.removeFromParent();\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// ==================== Render Sync System ====================\n\t// Updates PixiJS objects from world transforms and visibility\n\trendererBundle\n\t\t.addSystem('renderer2d-sync')\n\t\t.setPriority(renderSyncPriority)\n\t\t.inPhase('render')\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('sprites', {\n\t\t\twith: ['sprite', 'worldTransform'],\n\t\t\tchanged: ['worldTransform'],\n\t\t})\n\t\t.addQuery('graphics', {\n\t\t\twith: ['graphics', 'worldTransform'],\n\t\t\tchanged: ['worldTransform'],\n\t\t})\n\t\t.addQuery('containers', {\n\t\t\twith: ['container', 'worldTransform'],\n\t\t\tchanged: ['worldTransform'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\t// Process sprites\n\t\t\tfor (const entity of queries.sprites) {\n\t\t\t\tconst { sprite, worldTransform } = entity.components;\n\n\t\t\t\tsprite.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tsprite.rotation = worldTransform.rotation;\n\t\t\t\tsprite.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visibleComp = ecs.entityManager.getComponent(entity.id, 'visible');\n\t\t\t\tif (visibleComp) {\n\t\t\t\t\tsprite.visible = visibleComp.visible;\n\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\tsprite.alpha = visibleComp.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Process graphics\n\t\t\tfor (const entity of queries.graphics) {\n\t\t\t\tconst { graphics, worldTransform } = entity.components;\n\n\t\t\t\tgraphics.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tgraphics.rotation = worldTransform.rotation;\n\t\t\t\tgraphics.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visibleComp = ecs.entityManager.getComponent(entity.id, 'visible');\n\t\t\t\tif (visibleComp) {\n\t\t\t\t\tgraphics.visible = visibleComp.visible;\n\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\tgraphics.alpha = visibleComp.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Process containers\n\t\t\tfor (const entity of queries.containers) {\n\t\t\t\tconst { container, worldTransform } = entity.components;\n\n\t\t\t\tcontainer.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tcontainer.rotation = worldTransform.rotation;\n\t\t\t\tcontainer.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visibleComp = ecs.entityManager.getComponent(entity.id, 'visible');\n\t\t\t\tif (visibleComp) {\n\t\t\t\t\tcontainer.visible = visibleComp.visible;\n\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\tcontainer.alpha = visibleComp.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// ==================== Scene Graph Manager System ====================\n\t// Sets up reactive queries to manage scene graph on entity create/destroy\n\t// High priority ensures this runs before user systems' onInitialize\n\trendererBundle\n\t\t.addSystem('renderer2d-scene-graph')\n\t\t.setPriority(9999)\n\t\t.inGroup(systemGroup)\n\t\t.setOnInitialize(async (ecs) => {\n\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\tconst rootCont = ecs.getResource('rootContainer');\n\n\t\t\t// Capture Container constructor via dynamic import (same module instance as pixi.js internals)\n\t\t\tconst { Container: ContainerClass } = await import('pixi.js');\n\t\t\tcreateLayerContainer = (label: string) => {\n\t\t\t\tconst cont = new ContainerClass();\n\t\t\t\tcont.label = label;\n\t\t\t\treturn cont;\n\t\t\t};\n\n\t\t\t// Create declared render layer containers in order (back-to-front)\n\t\t\tfor (const layerName of renderLayers) {\n\t\t\t\tconst cont = createLayerContainer(`layer:${layerName}`);\n\t\t\t\tlayerContainers.set(layerName, cont);\n\t\t\t\trootCont.addChild(cont);\n\t\t\t}\n\n\t\t\t// Reactive query for sprites\n\t\t\tecs.addReactiveQuery('renderer2d-sprites', {\n\t\t\t\twith: ['sprite'],\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.sprite;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tremoveFromSceneGraph(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Reactive query for graphics\n\t\t\tecs.addReactiveQuery('renderer2d-graphics', {\n\t\t\t\twith: ['graphics'],\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.graphics;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tremoveFromSceneGraph(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Reactive query for containers\n\t\t\tecs.addReactiveQuery('renderer2d-containers', {\n\t\t\t\twith: ['container'],\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.container;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tremoveFromSceneGraph(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Subscribe to hierarchy changes to mirror reparenting in scene graph\n\t\t\tecs.on('hierarchyChanged', ({ entityId }) => {\n\t\t\t\tupdateSceneGraphParent(entityId, ecs);\n\t\t\t});\n\n\t\t\t// Re-parent entity when render layer is added or changed\n\t\t\tecs.onComponentAdded('renderLayer', (_layerName, entity) => {\n\t\t\t\tupdateSceneGraphParent(entity.id, ecs);\n\t\t\t});\n\n\t\t\t// Re-parent entity when render layer is removed\n\t\t\tecs.onComponentRemoved('renderLayer', (_oldLayerName, entity) => {\n\t\t\t\tupdateSceneGraphParent(entity.id, ecs);\n\t\t\t});\n\n\t\t\t// Track screen bounds on resize\n\t\t\tpixiApp.renderer.on('resize', (width: number, height: number) => {\n\t\t\t\tconst bounds = ecs.getResource('bounds');\n\t\t\t\tbounds.width = width;\n\t\t\t\tbounds.height = height;\n\t\t\t});\n\n\t\t\t// Wire up the game loop if requested\n\t\t\tif (startLoop) {\n\t\t\t\tpixiApp.ticker.add((ticker) => {\n\t\t\t\t\tecs.update(ticker.deltaMS / 1_000);\n\t\t\t\t});\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// Merge transform bundle (runs first) with renderer bundle\n\tconst transformBundle = createTransformBundle(transformOptions);\n\treturn mergeBundles('renderer2d', transformBundle, rendererBundle);\n}\n"
|
|
10
6
|
],
|
|
11
|
-
"mappings": "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",
|
|
12
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "BB9BA06A6FF5407264756E2164756E21",
|
|
13
9
|
"names": []
|
|
14
10
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Reads worldTransform for position checking; modifies localTransform for corrections.
|
|
6
6
|
* Supports destroy, clamp, and wrap behaviors.
|
|
7
7
|
*/
|
|
8
|
-
import Bundle from '
|
|
9
|
-
import type { SystemPhase } from '
|
|
8
|
+
import { Bundle } from 'ecspresso';
|
|
9
|
+
import type { SystemPhase } from 'ecspresso';
|
|
10
10
|
import type { TransformComponentTypes } from './transform';
|
|
11
11
|
/**
|
|
12
12
|
* Component that marks an entity for destruction when outside bounds.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var q=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(I,F)=>(typeof require<"u"?require:I)[F]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{Bundle as E}from"ecspresso";function h(j,I,F,Q){let U={width:j,height:I};if(F!==void 0)U.x=F;if(Q!==void 0)U.y=Q;return U}function T(j){return{destroyOutOfBounds:j!==void 0?{padding:j}:{}}}function y(j){return{clampToBounds:j!==void 0?{margin:j}:{}}}function x(j){return{wrapAtBounds:j!==void 0?{padding:j}:{}}}function b(j){let{systemGroup:I="physics",priority:F=50,boundsResourceKey:Q="bounds",autoRemove:U=!0,phase:S="postUpdate"}=j??{},W=new E("bounds");return W.addSystem("bounds-destroy").setPriority(F).inPhase(S).inGroup(I).addQuery("entities",{with:["worldTransform","destroyOutOfBounds"]}).setProcess((A,B,J)=>{let C=J.getResource(Q),L=C.x??0,N=C.y??0,$=L+C.width,v=N+C.height;for(let P of A.entities){let{worldTransform:V,destroyOutOfBounds:z}=P.components,D=z.padding??0,k=R(V,L,N,$,v,D);if(!k)continue;if(J.eventBus.publish("entityOutOfBounds",{entityId:P.id,exitEdge:k}),U)J.commands.removeEntity(P.id)}}).and(),W.addSystem("bounds-clamp").setPriority(F-1).inPhase(S).inGroup(I).addQuery("entities",{with:["localTransform","worldTransform","clampToBounds"]}).setProcess((A,B,J)=>{let C=J.getResource(Q),L=C.x??0,N=C.y??0,$=L+C.width,v=N+C.height;for(let P of A.entities){let{localTransform:V,worldTransform:z,clampToBounds:D}=P.components,k=D.margin??0,Z=L+k,_=N+k,H=$-k,O=v-k,G=0,K=0;if(z.x<Z)G=Z-z.x;if(z.x>H)G=H-z.x;if(z.y<_)K=_-z.y;if(z.y>O)K=O-z.y;if(G!==0||K!==0)V.x+=G,V.y+=K,J.markChanged(P.id,"localTransform")}}).and(),W.addSystem("bounds-wrap").setPriority(F-2).inPhase(S).inGroup(I).addQuery("entities",{with:["localTransform","worldTransform","wrapAtBounds"]}).setProcess((A,B,J)=>{let C=J.getResource(Q),L=C.x??0,N=C.y??0,$=L+C.width,v=N+C.height;for(let P of A.entities){let{localTransform:V,worldTransform:z,wrapAtBounds:D}=P.components,k=D.padding??0,Z=0,_=0,H=$-L,O=v-N;if(z.x>$+k)Z=-(H+2*k);else if(z.x<L-k)Z=H+2*k;if(z.y>v+k)_=-(O+2*k);else if(z.y<N-k)_=O+2*k;if(Z!==0||_!==0)V.x+=Z,V.y+=_,J.markChanged(P.id,"localTransform")}}).and(),W}function R(j,I,F,Q,U,S){if(j.x>Q+S)return"right";if(j.x<I-S)return"left";if(j.y>U+S)return"bottom";if(j.y<F-S)return"top";return null}export{x as createWrapAtBounds,T as createDestroyOutOfBounds,y as createClampToBounds,b as createBoundsBundle,h as createBounds};
|
|
2
|
+
|
|
3
|
+
//# debugId=8FF1EB3AF1C6181164756E2164756E21
|
|
4
|
+
//# sourceMappingURL=bounds.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/utils/bounds.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Bounds Bundle for ECSpresso\n *\n * Provides screen bounds enforcement for entities with transforms.\n * Reads worldTransform for position checking; modifies localTransform for corrections.\n * Supports destroy, clamp, and wrap behaviors.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { TransformComponentTypes } from './transform';\n\n// ==================== Component Types ====================\n\n/**\n * Component that marks an entity for destruction when outside bounds.\n */\nexport interface DestroyOutOfBounds {\n\t/** Extra padding beyond bounds before destruction (default: 0) */\n\tpadding?: number;\n}\n\n/**\n * Component that clamps an entity's position to stay within bounds.\n */\nexport interface ClampToBounds {\n\t/** Margin to shrink the valid area (default: 0) */\n\tmargin?: number;\n}\n\n/**\n * Component that wraps an entity's position to the opposite edge.\n */\nexport interface WrapAtBounds {\n\t/** Padding beyond bounds before wrapping (default: 0) */\n\tpadding?: number;\n}\n\n/**\n * Component types provided by the bounds bundle.\n * Extend your component types with this interface.\n *\n * @example\n * ```typescript\n * interface GameComponents extends TransformComponentTypes, BoundsComponentTypes {\n * sprite: Sprite;\n * }\n * ```\n */\nexport interface BoundsComponentTypes {\n\tdestroyOutOfBounds: DestroyOutOfBounds;\n\tclampToBounds: ClampToBounds;\n\twrapAtBounds: WrapAtBounds;\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Bounds rectangle definition.\n */\nexport interface BoundsRect {\n\t/** Left edge x coordinate (default: 0) */\n\tx?: number;\n\t/** Top edge y coordinate (default: 0) */\n\ty?: number;\n\t/** Width of the bounds area */\n\twidth: number;\n\t/** Height of the bounds area */\n\theight: number;\n}\n\n/**\n * Resource types provided by the bounds bundle.\n */\nexport interface BoundsResourceTypes {\n\tbounds: BoundsRect;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when an entity exits bounds.\n */\nexport interface EntityOutOfBoundsEvent {\n\t/** The entity that exited bounds */\n\tentityId: number;\n\t/** The edge the entity exited through */\n\texitEdge: 'top' | 'bottom' | 'left' | 'right';\n}\n\n/**\n * Event types provided by the bounds bundle.\n */\nexport interface BoundsEventTypes {\n\tentityOutOfBounds: EntityOutOfBoundsEvent;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the bounds bundle.\n */\nexport interface BoundsBundleOptions {\n\t/** System group name (default: 'physics') */\n\tsystemGroup?: string;\n\t/** Priority for bounds systems (default: 50) */\n\tpriority?: number;\n\t/** Resource key for bounds rectangle (default: 'bounds') */\n\tboundsResourceKey?: string;\n\t/** Whether to auto-remove entities when out of bounds (default: true) */\n\tautoRemove?: boolean;\n\t/** Execution phase (default: 'postUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a bounds rectangle resource.\n *\n * @param width The width of the bounds area\n * @param height The height of the bounds area\n * @param x The left edge x coordinate (default: 0)\n * @param y The top edge y coordinate (default: 0)\n * @returns Bounds rectangle suitable for use as a resource\n *\n * @example\n * ```typescript\n * ECSpresso.create()\n * .withResource('bounds', createBounds(800, 600))\n * .build();\n * ```\n */\nexport function createBounds(width: number, height: number, x?: number, y?: number): BoundsRect {\n\tconst bounds: BoundsRect = { width, height };\n\tif (x !== undefined) bounds.x = x;\n\tif (y !== undefined) bounds.y = y;\n\treturn bounds;\n}\n\n/**\n * Create a destroyOutOfBounds component.\n *\n * @param padding Extra padding beyond bounds before destruction\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createDestroyOutOfBounds(20),\n * });\n * ```\n */\nexport function createDestroyOutOfBounds(padding?: number): Pick<BoundsComponentTypes, 'destroyOutOfBounds'> {\n\treturn {\n\t\tdestroyOutOfBounds: padding !== undefined ? { padding } : {},\n\t};\n}\n\n/**\n * Create a clampToBounds component.\n *\n * @param margin Margin to shrink the valid area\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createClampToBounds(30),\n * });\n * ```\n */\nexport function createClampToBounds(margin?: number): Pick<BoundsComponentTypes, 'clampToBounds'> {\n\treturn {\n\t\tclampToBounds: margin !== undefined ? { margin } : {},\n\t};\n}\n\n/**\n * Create a wrapAtBounds component.\n *\n * @param padding Padding beyond bounds before wrapping\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createWrapAtBounds(10),\n * });\n * ```\n */\nexport function createWrapAtBounds(padding?: number): Pick<BoundsComponentTypes, 'wrapAtBounds'> {\n\treturn {\n\t\twrapAtBounds: padding !== undefined ? { padding } : {},\n\t};\n}\n\n// ==================== Internal Types ====================\n\ntype CombinedComponentTypes = BoundsComponentTypes & TransformComponentTypes;\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a bounds bundle for ECSpresso.\n *\n * This bundle provides:\n * - Destroy out of bounds system - removes entities that exit bounds\n * - Clamp to bounds system - constrains entities within bounds\n * - Wrap at bounds system - wraps entities to opposite edge\n *\n * Uses worldTransform for position checking (world-space) and modifies\n * localTransform for corrections. Works best with entities that don't\n * have parent transforms (orphan entities).\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso\n * .create<Components, Events, Resources>()\n * .withResource('bounds', createBounds(800, 600))\n * .withBundle(createTransformBundle())\n * .withBundle(createBoundsBundle())\n * .build();\n *\n * // Entity that gets destroyed when leaving screen\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createDestroyOutOfBounds(),\n * });\n * ```\n */\nexport function createBoundsBundle<ResourceTypes extends BoundsResourceTypes = BoundsResourceTypes>(\n\toptions?: BoundsBundleOptions\n): Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes> {\n\tconst {\n\t\tsystemGroup = 'physics',\n\t\tpriority = 50,\n\t\tboundsResourceKey = 'bounds',\n\t\tautoRemove = true,\n\t\tphase = 'postUpdate',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes>('bounds');\n\n\t// Destroy out of bounds system\n\tbundle\n\t\t.addSystem('bounds-destroy')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['worldTransform', 'destroyOutOfBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { worldTransform, destroyOutOfBounds } = entity.components;\n\t\t\t\tconst padding = destroyOutOfBounds.padding ?? 0;\n\n\t\t\t\tconst exitEdge = getExitEdge(worldTransform, minX, minY, maxX, maxY, padding);\n\t\t\t\tif (!exitEdge) continue;\n\n\t\t\t\tecs.eventBus.publish('entityOutOfBounds', {\n\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\texitEdge,\n\t\t\t\t});\n\n\t\t\t\tif (autoRemove) {\n\t\t\t\t\tecs.commands.removeEntity(entity.id);\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// Clamp to bounds system\n\tbundle\n\t\t.addSystem('bounds-clamp')\n\t\t.setPriority(priority - 1)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['localTransform', 'worldTransform', 'clampToBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { localTransform, worldTransform, clampToBounds } = entity.components;\n\t\t\t\tconst margin = clampToBounds.margin ?? 0;\n\n\t\t\t\tconst clampedMinX = minX + margin;\n\t\t\t\tconst clampedMinY = minY + margin;\n\t\t\t\tconst clampedMaxX = maxX - margin;\n\t\t\t\tconst clampedMaxY = maxY - margin;\n\n\t\t\t\t// Calculate world-space correction and apply to local transform\n\t\t\t\t// For entities without parents, this is equivalent to direct position clamping\n\t\t\t\tlet deltaX = 0;\n\t\t\t\tlet deltaY = 0;\n\n\t\t\t\tif (worldTransform.x < clampedMinX) deltaX = clampedMinX - worldTransform.x;\n\t\t\t\tif (worldTransform.x > clampedMaxX) deltaX = clampedMaxX - worldTransform.x;\n\t\t\t\tif (worldTransform.y < clampedMinY) deltaY = clampedMinY - worldTransform.y;\n\t\t\t\tif (worldTransform.y > clampedMaxY) deltaY = clampedMaxY - worldTransform.y;\n\n\t\t\t\tif (deltaX !== 0 || deltaY !== 0) {\n\t\t\t\t\tlocalTransform.x += deltaX;\n\t\t\t\t\tlocalTransform.y += deltaY;\n\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// Wrap at bounds system\n\tbundle\n\t\t.addSystem('bounds-wrap')\n\t\t.setPriority(priority - 2)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['localTransform', 'worldTransform', 'wrapAtBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { localTransform, worldTransform, wrapAtBounds } = entity.components;\n\t\t\t\tconst padding = wrapAtBounds.padding ?? 0;\n\n\t\t\t\tlet deltaX = 0;\n\t\t\t\tlet deltaY = 0;\n\t\t\t\tconst boundsWidth = maxX - minX;\n\t\t\t\tconst boundsHeight = maxY - minY;\n\n\t\t\t\t// Wrap horizontally\n\t\t\t\tif (worldTransform.x > maxX + padding) {\n\t\t\t\t\tdeltaX = -(boundsWidth + 2 * padding);\n\t\t\t\t} else if (worldTransform.x < minX - padding) {\n\t\t\t\t\tdeltaX = boundsWidth + 2 * padding;\n\t\t\t\t}\n\n\t\t\t\t// Wrap vertically\n\t\t\t\tif (worldTransform.y > maxY + padding) {\n\t\t\t\t\tdeltaY = -(boundsHeight + 2 * padding);\n\t\t\t\t} else if (worldTransform.y < minY - padding) {\n\t\t\t\t\tdeltaY = boundsHeight + 2 * padding;\n\t\t\t\t}\n\n\t\t\t\tif (deltaX !== 0 || deltaY !== 0) {\n\t\t\t\t\tlocalTransform.x += deltaX;\n\t\t\t\t\tlocalTransform.y += deltaY;\n\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\treturn bundle;\n}\n\n/**\n * Determine which edge an entity has exited through, if any.\n */\nfunction getExitEdge(\n\ttransform: { x: number; y: number },\n\tminX: number,\n\tminY: number,\n\tmaxX: number,\n\tmaxY: number,\n\tpadding: number\n): 'top' | 'bottom' | 'left' | 'right' | null {\n\tif (transform.x > maxX + padding) return 'right';\n\tif (transform.x < minX - padding) return 'left';\n\tif (transform.y > maxY + padding) return 'bottom';\n\tif (transform.y < minY - padding) return 'top';\n\treturn null;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "8FF1EB3AF1C6181164756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Uses worldTransform for position (world-space collision).
|
|
6
6
|
* Supports AABB and circle colliders.
|
|
7
7
|
*/
|
|
8
|
-
import Bundle from '
|
|
9
|
-
import type { SystemPhase } from '
|
|
8
|
+
import { Bundle } from 'ecspresso';
|
|
9
|
+
import type { SystemPhase } from 'ecspresso';
|
|
10
10
|
import type { TransformComponentTypes } from './transform';
|
|
11
11
|
/**
|
|
12
12
|
* Axis-Aligned Bounding Box collider.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var L=((v)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(v,{get:(z,D)=>(typeof require<"u"?require:z)[D]}):v)(function(v){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+v+'" is not supported')});import{Bundle as B}from"ecspresso";function R(v,z,D,E){let I={width:v,height:z};if(D!==void 0)I.offsetX=D;if(E!==void 0)I.offsetY=E;return{aabbCollider:I}}function V(v,z,D){let E={radius:v};if(z!==void 0)E.offsetX=z;if(D!==void 0)E.offsetY=D;return{circleCollider:E}}function K(v,z){return{collisionLayer:{layer:v,collidesWith:z}}}function m(v){let z={};for(let D of Object.keys(v)){let E=v[D];z[D]=()=>K(D,E)}return z}function C(v){let{systemGroup:z="physics",priority:D=0,phase:E="postUpdate"}=v??{},I=new B("collision");return I.addSystem("collision-detection").setPriority(D).inPhase(E).inGroup(z).addQuery("collidables",{with:["worldTransform","collisionLayer"]}).setProcess((Z,_,O)=>{let N=[];for(let M of Z.collidables){let{worldTransform:J,collisionLayer:k}=M.components,F=O.entityManager.getComponent(M.id,"aabbCollider"),$=O.entityManager.getComponent(M.id,"circleCollider");if(!F&&!$)continue;let U={entityId:M.id,x:J.x,y:J.y,layer:k.layer,collidesWith:k.collidesWith};if(F)U.x+=F.offsetX??0,U.y+=F.offsetY??0,U.aabb={halfWidth:F.width/2,halfHeight:F.height/2};if($)U.x+=$.offsetX??0,U.y+=$.offsetY??0,U.circle={radius:$.radius};N.push(U)}let Q=new Set;for(let M=0;M<N.length;M++){let J=N[M];if(!J)continue;for(let k=M+1;k<N.length;k++){let F=N[k];if(!F)continue;let $=J.collidesWith.includes(F.layer),U=F.collidesWith.includes(J.layer);if(!$&&!U)continue;let G=J.entityId<F.entityId?`${J.entityId}:${F.entityId}`:`${F.entityId}:${J.entityId}`;if(Q.has(G))continue;if(P(J,F))Q.add(G),O.eventBus.publish("collision",{entityA:J.entityId,entityB:F.entityId,layerA:J.layer,layerB:F.layer})}}}).and(),I}function P(v,z){if(v.aabb&&z.aabb)return T(v.x,v.y,v.aabb.halfWidth,v.aabb.halfHeight,z.x,z.y,z.aabb.halfWidth,z.aabb.halfHeight);if(v.circle&&z.circle)return j(v.x,v.y,v.circle.radius,z.x,z.y,z.circle.radius);if(v.aabb&&z.circle)return A(v.x,v.y,v.aabb.halfWidth,v.aabb.halfHeight,z.x,z.y,z.circle.radius);if(v.circle&&z.aabb)return A(z.x,z.y,z.aabb.halfWidth,z.aabb.halfHeight,v.x,v.y,v.circle.radius);return!1}function T(v,z,D,E,I,Z,_,O){let N=Math.abs(v-I),Q=Math.abs(z-Z);return N<D+_&&Q<E+O}function j(v,z,D,E,I,Z){let _=v-E,O=z-I,N=_*_+O*O,Q=D+Z;return N<Q*Q}function A(v,z,D,E,I,Z,_){let O=Math.max(v-D,Math.min(I,v+D)),N=Math.max(z-E,Math.min(Z,z+E)),Q=I-O,M=Z-N;return Q*Q+M*M<_*_}export{m as defineCollisionLayers,K as createCollisionLayer,C as createCollisionBundle,V as createCircleCollider,R as createAABBCollider};
|
|
2
|
+
|
|
3
|
+
//# debugId=87C006826612A65764756E2164756E21
|
|
4
|
+
//# sourceMappingURL=collision.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/utils/collision.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Collision Bundle for ECSpresso\n *\n * Provides layer-based collision detection with events.\n * Uses worldTransform for position (world-space collision).\n * Supports AABB and circle colliders.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { TransformComponentTypes } from './transform';\n\n// ==================== Component Types ====================\n\n/**\n * Axis-Aligned Bounding Box collider.\n */\nexport interface AABBCollider {\n\t/** Width of the bounding box */\n\twidth: number;\n\t/** Height of the bounding box */\n\theight: number;\n\t/** X offset from entity position (default: 0) */\n\toffsetX?: number;\n\t/** Y offset from entity position (default: 0) */\n\toffsetY?: number;\n}\n\n/**\n * Circle collider.\n */\nexport interface CircleCollider {\n\t/** Radius of the circle */\n\tradius: number;\n\t/** X offset from entity position (default: 0) */\n\toffsetX?: number;\n\t/** Y offset from entity position (default: 0) */\n\toffsetY?: number;\n}\n\n/**\n * Collision layer configuration.\n */\nexport interface CollisionLayer {\n\t/** The layer this entity belongs to */\n\tlayer: string;\n\t/** Layers this entity can collide with */\n\tcollidesWith: readonly string[];\n}\n\n/**\n * Component types provided by the collision bundle.\n * Extend your component types with this interface.\n *\n * @example\n * ```typescript\n * interface GameComponents extends TransformComponentTypes, CollisionComponentTypes {\n * sprite: Sprite;\n * enemy: boolean;\n * }\n * ```\n */\nexport interface CollisionComponentTypes {\n\taabbCollider: AABBCollider;\n\tcircleCollider: CircleCollider;\n\tcollisionLayer: CollisionLayer;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when two entities collide.\n */\nexport interface CollisionEvent {\n\t/** First entity in the collision */\n\tentityA: number;\n\t/** Second entity in the collision */\n\tentityB: number;\n\t/** Layer of the first entity */\n\tlayerA: string;\n\t/** Layer of the second entity */\n\tlayerB: string;\n}\n\n/**\n * Event types provided by the collision bundle.\n */\nexport interface CollisionEventTypes {\n\tcollision: CollisionEvent;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the collision bundle.\n */\nexport interface CollisionBundleOptions {\n\t/** System group name (default: 'physics') */\n\tsystemGroup?: string;\n\t/** Priority for collision system (default: 0) */\n\tpriority?: number;\n\t/** Name of the collision event (default: 'collision') */\n\tcollisionEventName?: string;\n\t/** Execution phase (default: 'postUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create an AABB collider component.\n *\n * @param width Width of the bounding box\n * @param height Height of the bounding box\n * @param offsetX X offset from entity position\n * @param offsetY Y offset from entity position\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * });\n * ```\n */\nexport function createAABBCollider(\n\twidth: number,\n\theight: number,\n\toffsetX?: number,\n\toffsetY?: number\n): Pick<CollisionComponentTypes, 'aabbCollider'> {\n\tconst collider: AABBCollider = { width, height };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\treturn { aabbCollider: collider };\n}\n\n/**\n * Create a circle collider component.\n *\n * @param radius Radius of the circle\n * @param offsetX X offset from entity position\n * @param offsetY Y offset from entity position\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createCircleCollider(25),\n * });\n * ```\n */\nexport function createCircleCollider(\n\tradius: number,\n\toffsetX?: number,\n\toffsetY?: number\n): Pick<CollisionComponentTypes, 'circleCollider'> {\n\tconst collider: CircleCollider = { radius };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\treturn { circleCollider: collider };\n}\n\n/**\n * Create a collision layer component.\n *\n * @param layer The layer this entity belongs to\n * @param collidesWith Layers this entity can collide with\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...createCollisionLayer('player', ['enemy', 'obstacle']),\n * });\n * ```\n */\nexport function createCollisionLayer(\n\tlayer: string,\n\tcollidesWith: readonly string[]\n): Pick<CollisionComponentTypes, 'collisionLayer'> {\n\treturn {\n\t\tcollisionLayer: { layer, collidesWith },\n\t};\n}\n\n/**\n * Layer factory result from defineCollisionLayers.\n */\nexport type LayerFactories<T extends Record<string, readonly string[]>> = {\n\t[K in keyof T]: () => Pick<CollisionComponentTypes, 'collisionLayer'>;\n};\n\n/**\n * Define collision layer relationships and get factory functions.\n *\n * @param rules Object mapping layer names to arrays of layers they collide with\n * @returns Object with factory functions for each layer\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({\n * player: ['enemy', 'enemyProjectile'],\n * playerProjectile: ['enemy'],\n * enemy: ['playerProjectile'],\n * enemyProjectile: ['player'],\n * });\n *\n * // Usage\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...layers.player(),\n * });\n * ```\n */\nexport function defineCollisionLayers<T extends Record<string, readonly string[]>>(\n\trules: T\n): LayerFactories<T> {\n\tconst factories = {} as LayerFactories<T>;\n\n\tfor (const layer of Object.keys(rules) as Array<keyof T & string>) {\n\t\tconst collidesWith = rules[layer] as readonly string[];\n\t\tfactories[layer] = () => createCollisionLayer(layer, collidesWith);\n\t}\n\n\treturn factories;\n}\n\n// ==================== Internal Types ====================\n\ntype CombinedComponentTypes = CollisionComponentTypes & TransformComponentTypes;\n\ninterface ColliderInfo {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\tlayer: string;\n\tcollidesWith: readonly string[];\n\taabb?: { halfWidth: number; halfHeight: number };\n\tcircle?: { radius: number };\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a collision bundle for ECSpresso.\n *\n * This bundle provides:\n * - O(n²) collision detection between entities with colliders\n * - AABB-AABB, circle-circle, and AABB-circle collision\n * - Layer-based filtering for collision pairs\n * - Deduplication of A-B / B-A collisions\n *\n * Uses worldTransform for position (world-space collision detection).\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso\n * .create<Components, Events, Resources>()\n * .withBundle(createTransformBundle())\n * .withBundle(createCollisionBundle())\n * .build();\n *\n * // Entity with collision\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...createCollisionLayer('player', ['enemy']),\n * });\n * ```\n */\nexport function createCollisionBundle(\n\toptions?: CollisionBundleOptions\n): Bundle<CombinedComponentTypes, CollisionEventTypes, {}> {\n\tconst {\n\t\tsystemGroup = 'physics',\n\t\tpriority = 0,\n\t\tphase = 'postUpdate',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<CombinedComponentTypes, CollisionEventTypes, {}>('collision');\n\n\tbundle\n\t\t.addSystem('collision-detection')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('collidables', {\n\t\t\twith: ['worldTransform', 'collisionLayer'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\t// Build list of collidable entities with their computed positions\n\t\t\tconst colliders: ColliderInfo[] = [];\n\n\t\t\tfor (const entity of queries.collidables) {\n\t\t\t\tconst { worldTransform, collisionLayer } = entity.components;\n\n\t\t\t\t// Get collider info\n\t\t\t\tconst aabb = ecs.entityManager.getComponent(entity.id, 'aabbCollider') as AABBCollider | null;\n\t\t\t\tconst circle = ecs.entityManager.getComponent(entity.id, 'circleCollider') as CircleCollider | null;\n\n\t\t\t\t// Must have at least one collider\n\t\t\t\tif (!aabb && !circle) continue;\n\n\t\t\t\tconst info: ColliderInfo = {\n\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\tx: worldTransform.x,\n\t\t\t\t\ty: worldTransform.y,\n\t\t\t\t\tlayer: collisionLayer.layer,\n\t\t\t\t\tcollidesWith: collisionLayer.collidesWith,\n\t\t\t\t};\n\n\t\t\t\tif (aabb) {\n\t\t\t\t\tinfo.x += aabb.offsetX ?? 0;\n\t\t\t\t\tinfo.y += aabb.offsetY ?? 0;\n\t\t\t\t\tinfo.aabb = {\n\t\t\t\t\t\thalfWidth: aabb.width / 2,\n\t\t\t\t\t\thalfHeight: aabb.height / 2,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (circle) {\n\t\t\t\t\tinfo.x += circle.offsetX ?? 0;\n\t\t\t\t\tinfo.y += circle.offsetY ?? 0;\n\t\t\t\t\tinfo.circle = { radius: circle.radius };\n\t\t\t\t}\n\n\t\t\t\tcolliders.push(info);\n\t\t\t}\n\n\t\t\t// Track processed pairs to avoid duplicates\n\t\t\tconst processedPairs = new Set<string>();\n\n\t\t\t// O(n²) collision detection\n\t\t\tfor (let i = 0; i < colliders.length; i++) {\n\t\t\t\tconst a = colliders[i];\n\t\t\t\tif (!a) continue;\n\n\t\t\t\tfor (let j = i + 1; j < colliders.length; j++) {\n\t\t\t\t\tconst b = colliders[j];\n\t\t\t\t\tif (!b) continue;\n\n\t\t\t\t\t// Check layer compatibility (A→B or B→A)\n\t\t\t\t\tconst aCollidesWithB = a.collidesWith.includes(b.layer);\n\t\t\t\t\tconst bCollidesWithA = b.collidesWith.includes(a.layer);\n\n\t\t\t\t\tif (!aCollidesWithB && !bCollidesWithA) continue;\n\n\t\t\t\t\t// Create unique pair key\n\t\t\t\t\tconst pairKey = a.entityId < b.entityId\n\t\t\t\t\t\t? `${a.entityId}:${b.entityId}`\n\t\t\t\t\t\t: `${b.entityId}:${a.entityId}`;\n\n\t\t\t\t\tif (processedPairs.has(pairKey)) continue;\n\n\t\t\t\t\t// Check collision based on collider types\n\t\t\t\t\tconst colliding = checkCollision(a, b);\n\n\t\t\t\t\tif (colliding) {\n\t\t\t\t\t\tprocessedPairs.add(pairKey);\n\t\t\t\t\t\tecs.eventBus.publish('collision', {\n\t\t\t\t\t\t\tentityA: a.entityId,\n\t\t\t\t\t\t\tentityB: b.entityId,\n\t\t\t\t\t\t\tlayerA: a.layer,\n\t\t\t\t\t\t\tlayerB: b.layer,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\treturn bundle;\n}\n\n/**\n * Check if two colliders are overlapping.\n */\nfunction checkCollision(a: ColliderInfo, b: ColliderInfo): boolean {\n\t// AABB vs AABB\n\tif (a.aabb && b.aabb) {\n\t\treturn aabbVsAabb(\n\t\t\ta.x, a.y, a.aabb.halfWidth, a.aabb.halfHeight,\n\t\t\tb.x, b.y, b.aabb.halfWidth, b.aabb.halfHeight\n\t\t);\n\t}\n\n\t// Circle vs Circle\n\tif (a.circle && b.circle) {\n\t\treturn circleVsCircle(\n\t\t\ta.x, a.y, a.circle.radius,\n\t\t\tb.x, b.y, b.circle.radius\n\t\t);\n\t}\n\n\t// AABB vs Circle\n\tif (a.aabb && b.circle) {\n\t\treturn aabbVsCircle(\n\t\t\ta.x, a.y, a.aabb.halfWidth, a.aabb.halfHeight,\n\t\t\tb.x, b.y, b.circle.radius\n\t\t);\n\t}\n\n\tif (a.circle && b.aabb) {\n\t\treturn aabbVsCircle(\n\t\t\tb.x, b.y, b.aabb.halfWidth, b.aabb.halfHeight,\n\t\t\ta.x, a.y, a.circle.radius\n\t\t);\n\t}\n\n\treturn false;\n}\n\n/**\n * AABB vs AABB collision test.\n */\nfunction aabbVsAabb(\n\tax: number, ay: number, aHalfWidth: number, aHalfHeight: number,\n\tbx: number, by: number, bHalfWidth: number, bHalfHeight: number\n): boolean {\n\tconst dx = Math.abs(ax - bx);\n\tconst dy = Math.abs(ay - by);\n\treturn dx < (aHalfWidth + bHalfWidth) && dy < (aHalfHeight + bHalfHeight);\n}\n\n/**\n * Circle vs Circle collision test.\n */\nfunction circleVsCircle(\n\tax: number, ay: number, aRadius: number,\n\tbx: number, by: number, bRadius: number\n): boolean {\n\tconst dx = ax - bx;\n\tconst dy = ay - by;\n\tconst distSq = dx * dx + dy * dy;\n\tconst radiusSum = aRadius + bRadius;\n\treturn distSq < radiusSum * radiusSum;\n}\n\n/**\n * AABB vs Circle collision test.\n */\nfunction aabbVsCircle(\n\taabbX: number, aabbY: number, halfWidth: number, halfHeight: number,\n\tcircleX: number, circleY: number, radius: number\n): boolean {\n\t// Find the closest point on the AABB to the circle center\n\tconst closestX = Math.max(aabbX - halfWidth, Math.min(circleX, aabbX + halfWidth));\n\tconst closestY = Math.max(aabbY - halfHeight, Math.min(circleY, aabbY + halfHeight));\n\n\t// Calculate distance from closest point to circle center\n\tconst dx = circleX - closestX;\n\tconst dy = circleY - closestY;\n\tconst distSq = dx * dx + dy * dy;\n\n\treturn distSq < radius * radius;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "87C006826612A65764756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|