@thewhateverapp/tile-sdk 0.14.9 → 0.14.11

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.
@@ -2,21 +2,42 @@
2
2
  * Pixi.js game components for tile-sdk
3
3
  *
4
4
  * Import from '@thewhateverapp/tile-sdk/pixi' to use pixi.js features.
5
- * This is a separate entry point to avoid loading pixi.js when not needed.
5
+ * This uses pixi.js directly (no @pixi/react) for better stability.
6
6
  *
7
7
  * @example
8
8
  * ```tsx
9
- * import { PixiGame, useGameLoop, Container, Graphics } from '@thewhateverapp/tile-sdk/pixi';
9
+ * import { PixiGame, usePixiApp, useGameLoop } from '@thewhateverapp/tile-sdk/pixi';
10
+ * import * as PIXI from 'pixi.js';
10
11
  *
11
12
  * function MyGame() {
12
13
  * return (
13
- * <PixiGame>
14
+ * <PixiGame width={256} height={554}>
14
15
  * <GameContent />
15
16
  * </PixiGame>
16
17
  * );
17
18
  * }
19
+ *
20
+ * function GameContent() {
21
+ * const app = usePixiApp();
22
+ *
23
+ * useEffect(() => {
24
+ * if (!app) return;
25
+ * const graphics = new PIXI.Graphics();
26
+ * graphics.beginFill(0xff0000);
27
+ * graphics.drawRect(100, 100, 50, 50);
28
+ * graphics.endFill();
29
+ * app.stage.addChild(graphics);
30
+ * return () => graphics.destroy();
31
+ * }, [app]);
32
+ *
33
+ * useGameLoop((delta) => {
34
+ * // Game logic runs every frame
35
+ * });
36
+ *
37
+ * return null;
38
+ * }
18
39
  * ```
19
40
  */
20
- export { PixiGame, useGameLoop, useGameState, useGameInput, Container, Sprite, Graphics, Text, AnimatedSprite, TilingSprite, NineSlicePlane, useApp, TILE_WIDTH, TILE_HEIGHT, } from '../react/PixiGame.js';
41
+ export { PixiGame, usePixiApp, useApp, useGameLoop, useGameState, useGameInput, Container, Graphics, Text, Sprite, TILE_WIDTH, TILE_HEIGHT, } from '../react/PixiGame.js';
21
42
  export type { PixiGameProps } from '../react/PixiGame.js';
22
43
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pixi/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,YAAY,EAEZ,SAAS,EACT,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,cAAc,EACd,YAAY,EACZ,cAAc,EACd,MAAM,EAEN,UAAU,EACV,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pixi/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EACL,QAAQ,EACR,UAAU,EACV,MAAM,EACN,WAAW,EACX,YAAY,EACZ,YAAY,EAEZ,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,MAAM,EAEN,UAAU,EACV,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
@@ -3,23 +3,44 @@
3
3
  * Pixi.js game components for tile-sdk
4
4
  *
5
5
  * Import from '@thewhateverapp/tile-sdk/pixi' to use pixi.js features.
6
- * This is a separate entry point to avoid loading pixi.js when not needed.
6
+ * This uses pixi.js directly (no @pixi/react) for better stability.
7
7
  *
8
8
  * @example
9
9
  * ```tsx
10
- * import { PixiGame, useGameLoop, Container, Graphics } from '@thewhateverapp/tile-sdk/pixi';
10
+ * import { PixiGame, usePixiApp, useGameLoop } from '@thewhateverapp/tile-sdk/pixi';
11
+ * import * as PIXI from 'pixi.js';
11
12
  *
12
13
  * function MyGame() {
13
14
  * return (
14
- * <PixiGame>
15
+ * <PixiGame width={256} height={554}>
15
16
  * <GameContent />
16
17
  * </PixiGame>
17
18
  * );
18
19
  * }
20
+ *
21
+ * function GameContent() {
22
+ * const app = usePixiApp();
23
+ *
24
+ * useEffect(() => {
25
+ * if (!app) return;
26
+ * const graphics = new PIXI.Graphics();
27
+ * graphics.beginFill(0xff0000);
28
+ * graphics.drawRect(100, 100, 50, 50);
29
+ * graphics.endFill();
30
+ * app.stage.addChild(graphics);
31
+ * return () => graphics.destroy();
32
+ * }, [app]);
33
+ *
34
+ * useGameLoop((delta) => {
35
+ * // Game logic runs every frame
36
+ * });
37
+ *
38
+ * return null;
39
+ * }
19
40
  * ```
20
41
  */
21
- export { PixiGame, useGameLoop, useGameState, useGameInput,
22
- // Re-exported @pixi/react components
23
- Container, Sprite, Graphics, Text, AnimatedSprite, TilingSprite, NineSlicePlane, useApp,
42
+ export { PixiGame, usePixiApp, useApp, useGameLoop, useGameState, useGameInput,
43
+ // Re-exported PIXI classes
44
+ Container, Graphics, Text, Sprite,
24
45
  // Constants
25
46
  TILE_WIDTH, TILE_HEIGHT, } from '../react/PixiGame.js';
@@ -1,14 +1,13 @@
1
1
  /**
2
- * PixiGame - A wrapper component that abstracts @pixi/react v7's Stage boilerplate
2
+ * PixiGame - Direct pixi.js integration without @pixi/react
3
3
  *
4
- * @pixi/react v7 uses the Stage component which provides the Application context.
5
- * Hooks like useTick must be used INSIDE the Stage tree.
6
- *
7
- * This component handles all of that setup automatically.
4
+ * This component creates a PIXI.Application imperatively and provides
5
+ * it via React context. This avoids react-reconciler issues that plague
6
+ * @pixi/react.
8
7
  *
9
8
  * @example
10
9
  * ```tsx
11
- * import { PixiGame, useGameLoop, Container, Graphics } from '@thewhateverapp/tile-sdk';
10
+ * import { PixiGame, usePixiApp, useGameLoop } from '@thewhateverapp/tile-sdk/pixi';
12
11
  *
13
12
  * function MyGame() {
14
13
  * return (
@@ -19,34 +18,51 @@
19
18
  * }
20
19
  *
21
20
  * function GameContent() {
22
- * const playerRef = useRef({ x: 128, y: 400 });
21
+ * const app = usePixiApp();
22
+ * const containerRef = useRef<PIXI.Container | null>(null);
23
+ *
24
+ * useEffect(() => {
25
+ * if (!app) return;
26
+ * const container = new PIXI.Container();
27
+ * app.stage.addChild(container);
28
+ * containerRef.current = container;
29
+ * return () => {
30
+ * app.stage.removeChild(container);
31
+ * container.destroy();
32
+ * };
33
+ * }, [app]);
23
34
  *
24
35
  * useGameLoop((delta) => {
25
- * // Game logic here - runs every frame
26
- * playerRef.current.x += 1 * delta;
36
+ * // Game logic here
27
37
  * });
28
38
  *
29
- * return (
30
- * <Container>
31
- * <Graphics draw={(g) => {
32
- * g.beginFill(0xff0000);
33
- * g.drawRect(playerRef.current.x, playerRef.current.y, 32, 32);
34
- * g.endFill();
35
- * }} />
36
- * </Container>
37
- * );
39
+ * return null; // No React children needed - pixi manages rendering
38
40
  * }
39
41
  * ```
40
42
  */
41
- import React, { ReactNode } from 'react';
42
- import { Container, Sprite, Graphics, Text, AnimatedSprite, TilingSprite, NineSlicePlane, useApp } from '@pixi/react';
43
- export { Container, Sprite, Graphics, Text, AnimatedSprite, TilingSprite, NineSlicePlane };
44
- export { useApp };
43
+ import React, { type ReactNode } from 'react';
44
+ import * as PIXI from 'pixi.js';
45
+ export { Container, Graphics, Text, Sprite } from 'pixi.js';
45
46
  /**
46
47
  * Tile dimensions - standard tile size
47
48
  */
48
49
  export declare const TILE_WIDTH = 256;
49
50
  export declare const TILE_HEIGHT = 554;
51
+ /**
52
+ * Hook to get the PIXI Application
53
+ */
54
+ export declare function usePixiApp(): PIXI.Application | null;
55
+ /**
56
+ * Hook to get the PIXI Application (alias for backwards compatibility)
57
+ */
58
+ export declare function useApp(): PIXI.Application | null;
59
+ /**
60
+ * useGameLoop - Run a callback every frame
61
+ *
62
+ * @param callback - Function called every frame with delta time (in frames, ~1 at 60fps)
63
+ * @param enabled - Whether the loop is active (default: true)
64
+ */
65
+ export declare function useGameLoop(callback: (delta: number) => void, enabled?: boolean): void;
50
66
  export interface PixiGameProps {
51
67
  children: ReactNode;
52
68
  /** Width in pixels (default: 256 for tile) */
@@ -64,93 +80,26 @@ export interface PixiGameProps {
64
80
  /** Whether game is paused */
65
81
  paused?: boolean;
66
82
  /** Callback when Application is ready */
67
- onMount?: (app: any) => void;
83
+ onMount?: (app: PIXI.Application) => void;
68
84
  }
69
85
  /**
70
86
  * PixiGame - Main wrapper component for pixi.js games
71
87
  *
72
- * Provides the Stage/Application context that @pixi/react hooks need.
73
- * All game content should be rendered as children of this component.
74
- *
75
- * Use pixi components directly:
76
- * - <Container> - Container for grouping elements
77
- * - <Sprite texture={...}> - Sprite with texture
78
- * - <Graphics draw={...}> - Graphics with draw callback
79
- * - <Text text="..." style={...}> - Text element
88
+ * Creates a PIXI.Application and provides it via context.
89
+ * Children can use usePixiApp() to access the app and create pixi objects.
80
90
  */
81
91
  export declare function PixiGame({ children, width, height, background, options, paused, onMount, }: PixiGameProps): React.JSX.Element;
82
- /**
83
- * useGameLoop - A more intuitive name for useTick
84
- *
85
- * Runs a callback every frame with delta time.
86
- * Must be used inside a PixiGame component (or any @pixi/react Stage).
87
- *
88
- * @param callback - Function called every frame with delta time (in frames, ~1 at 60fps)
89
- * @param enabled - Whether the loop is active (default: true)
90
- *
91
- * @example
92
- * ```tsx
93
- * function GameContent() {
94
- * const posRef = useRef({ x: 0, y: 0 });
95
- *
96
- * useGameLoop((delta) => {
97
- * posRef.current.x += 2 * delta;
98
- * if (posRef.current.x > 256) posRef.current.x = 0;
99
- * });
100
- *
101
- * return <Sprite x={posRef.current.x} y={posRef.current.y} texture={...} />;
102
- * }
103
- * ```
104
- */
105
- export declare function useGameLoop(callback: (delta: number) => void, enabled?: boolean): void;
106
92
  /**
107
93
  * useGameState - Helper for managing game state with refs
108
94
  *
109
95
  * Returns a ref and a forceUpdate function for when you need to trigger re-renders.
110
96
  * Use refs for continuous game state (position, velocity) to avoid re-render loops.
111
- *
112
- * @example
113
- * ```tsx
114
- * function GameContent() {
115
- * const [state, forceUpdate] = useGameState({
116
- * playerX: 128,
117
- * playerY: 400,
118
- * score: 0,
119
- * });
120
- *
121
- * useGameLoop((delta) => {
122
- * state.current.playerX += 1 * delta;
123
- * if (scoreChanged) {
124
- * forceUpdate(); // Only re-render when score changes
125
- * }
126
- * });
127
- *
128
- * return <Text text={`Score: ${state.current.score}`} />;
129
- * }
130
- * ```
131
97
  */
132
98
  export declare function useGameState<T extends object>(initialState: T): [React.MutableRefObject<T>, () => void];
133
99
  /**
134
100
  * useGameInput - Simple keyboard input hook for games
135
101
  *
136
102
  * Returns a ref with currently pressed keys.
137
- * Can be used inside or outside PixiGame component.
138
- *
139
- * @example
140
- * ```tsx
141
- * function GameContent() {
142
- * const keys = useGameInput();
143
- * const posRef = useRef({ x: 128, y: 400 });
144
- *
145
- * useGameLoop((delta) => {
146
- * if (keys.current.ArrowLeft) posRef.current.x -= 5 * delta;
147
- * if (keys.current.ArrowRight) posRef.current.x += 5 * delta;
148
- * if (keys.current[' ']) jump(); // Space key
149
- * });
150
- *
151
- * return <Sprite x={posRef.current.x} y={posRef.current.y} texture={...} />;
152
- * }
153
- * ```
154
103
  */
155
104
  export declare function useGameInput(): React.MutableRefObject<Record<string, boolean>>;
156
105
  //# sourceMappingURL=PixiGame.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PixiGame.d.ts","sourceRoot":"","sources":["../../src/react/PixiGame.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,EAAE,SAAS,EAAkC,MAAM,OAAO,CAAC;AACzE,OAAO,EAEL,SAAS,EACT,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,cAAc,EACd,YAAY,EACZ,cAAc,EAEd,MAAM,EACP,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC;AAG3F,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB;;GAEG;AACH,eAAO,MAAM,UAAU,MAAM,CAAC;AAC9B,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,SAAS,CAAC;IACpB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,CAAC;IACF,6BAA6B;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,KAAkB,EAClB,MAAoB,EACpB,UAAqB,EACrB,OAAY,EACZ,MAAc,EACd,OAAO,GACR,EAAE,aAAa,qBAkBf;AA2BD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,OAAO,GAAE,OAAc,QAGxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAC3C,YAAY,EAAE,CAAC,GACd,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CASzC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,oDAwB3B"}
1
+ {"version":3,"file":"PixiGame.d.ts","sourceRoot":"","sources":["../../src/react/PixiGame.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,IAAI,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,UAAU,MAAM,CAAC;AAC9B,eAAO,MAAM,WAAW,MAAM,CAAC;AAY/B;;GAEG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAAC,WAAW,GAAG,IAAI,CAMpD;AAED;;GAEG;AACH,wBAAgB,MAAM,IAAI,IAAI,CAAC,WAAW,GAAG,IAAI,CAEhD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,OAAO,GAAE,OAAc,QAexB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,SAAS,CAAC;IACpB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,CAAC;IACF,6BAA6B;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC;CAC3C;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,KAAkB,EAClB,MAAoB,EACpB,UAAqB,EACrB,OAAY,EACZ,MAAc,EACd,OAAO,GACR,EAAE,aAAa,qBAqFf;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAC3C,YAAY,EAAE,CAAC,GACd,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CASzC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,oDAwB3B"}
@@ -1,15 +1,14 @@
1
1
  'use client';
2
2
  /**
3
- * PixiGame - A wrapper component that abstracts @pixi/react v7's Stage boilerplate
3
+ * PixiGame - Direct pixi.js integration without @pixi/react
4
4
  *
5
- * @pixi/react v7 uses the Stage component which provides the Application context.
6
- * Hooks like useTick must be used INSIDE the Stage tree.
7
- *
8
- * This component handles all of that setup automatically.
5
+ * This component creates a PIXI.Application imperatively and provides
6
+ * it via React context. This avoids react-reconciler issues that plague
7
+ * @pixi/react.
9
8
  *
10
9
  * @example
11
10
  * ```tsx
12
- * import { PixiGame, useGameLoop, Container, Graphics } from '@thewhateverapp/tile-sdk';
11
+ * import { PixiGame, usePixiApp, useGameLoop } from '@thewhateverapp/tile-sdk/pixi';
13
12
  *
14
13
  * function MyGame() {
15
14
  * return (
@@ -20,130 +19,157 @@
20
19
  * }
21
20
  *
22
21
  * function GameContent() {
23
- * const playerRef = useRef({ x: 128, y: 400 });
22
+ * const app = usePixiApp();
23
+ * const containerRef = useRef<PIXI.Container | null>(null);
24
+ *
25
+ * useEffect(() => {
26
+ * if (!app) return;
27
+ * const container = new PIXI.Container();
28
+ * app.stage.addChild(container);
29
+ * containerRef.current = container;
30
+ * return () => {
31
+ * app.stage.removeChild(container);
32
+ * container.destroy();
33
+ * };
34
+ * }, [app]);
24
35
  *
25
36
  * useGameLoop((delta) => {
26
- * // Game logic here - runs every frame
27
- * playerRef.current.x += 1 * delta;
37
+ * // Game logic here
28
38
  * });
29
39
  *
30
- * return (
31
- * <Container>
32
- * <Graphics draw={(g) => {
33
- * g.beginFill(0xff0000);
34
- * g.drawRect(playerRef.current.x, playerRef.current.y, 32, 32);
35
- * g.endFill();
36
- * }} />
37
- * </Container>
38
- * );
40
+ * return null; // No React children needed - pixi manages rendering
39
41
  * }
40
42
  * ```
41
43
  */
42
- import React, { useCallback, useRef, useEffect } from 'react';
43
- import { Stage, Container, Sprite, Graphics, Text, AnimatedSprite, TilingSprite, NineSlicePlane, useTick, useApp, } from '@pixi/react';
44
- // Re-export @pixi/react components for convenience
45
- export { Container, Sprite, Graphics, Text, AnimatedSprite, TilingSprite, NineSlicePlane };
46
- // Re-export useApp for advanced use cases
47
- export { useApp };
44
+ import React, { createContext, useContext, useRef, useEffect, useState, useCallback, } from 'react';
45
+ import * as PIXI from 'pixi.js';
46
+ // Re-export PIXI classes for convenience
47
+ export { Container, Graphics, Text, Sprite } from 'pixi.js';
48
48
  /**
49
49
  * Tile dimensions - standard tile size
50
50
  */
51
51
  export const TILE_WIDTH = 256;
52
52
  export const TILE_HEIGHT = 554;
53
+ const PixiContext = createContext(null);
53
54
  /**
54
- * PixiGame - Main wrapper component for pixi.js games
55
- *
56
- * Provides the Stage/Application context that @pixi/react hooks need.
57
- * All game content should be rendered as children of this component.
58
- *
59
- * Use pixi components directly:
60
- * - <Container> - Container for grouping elements
61
- * - <Sprite texture={...}> - Sprite with texture
62
- * - <Graphics draw={...}> - Graphics with draw callback
63
- * - <Text text="..." style={...}> - Text element
55
+ * Hook to get the PIXI Application
64
56
  */
65
- export function PixiGame({ children, width = TILE_WIDTH, height = TILE_HEIGHT, background = 0x000000, options = {}, paused = false, onMount, }) {
66
- const stageOptions = {
67
- antialias: options.antialias ?? true,
68
- resolution: options.resolution ?? (typeof window !== 'undefined' ? window.devicePixelRatio : 1),
69
- autoDensity: options.autoDensity ?? true,
70
- backgroundColor: background,
71
- };
72
- return (React.createElement(Stage, { width: width, height: height, options: stageOptions, onMount: onMount },
73
- React.createElement(GameWrapper, { paused: paused }, children)));
57
+ export function usePixiApp() {
58
+ const context = useContext(PixiContext);
59
+ if (!context) {
60
+ throw new Error('usePixiApp must be used within a PixiGame component');
61
+ }
62
+ return context.app;
74
63
  }
75
64
  /**
76
- * Internal wrapper that provides pause functionality
65
+ * Hook to get the PIXI Application (alias for backwards compatibility)
77
66
  */
78
- function GameWrapper({ children, paused, }) {
79
- const app = useApp();
80
- useEffect(() => {
81
- if (app?.ticker) {
82
- if (paused) {
83
- app.ticker.stop();
84
- }
85
- else {
86
- app.ticker.start();
87
- }
88
- }
89
- }, [app, paused]);
90
- return React.createElement(React.Fragment, null, children);
67
+ export function useApp() {
68
+ return usePixiApp();
91
69
  }
92
70
  /**
93
- * useGameLoop - A more intuitive name for useTick
94
- *
95
- * Runs a callback every frame with delta time.
96
- * Must be used inside a PixiGame component (or any @pixi/react Stage).
71
+ * useGameLoop - Run a callback every frame
97
72
  *
98
73
  * @param callback - Function called every frame with delta time (in frames, ~1 at 60fps)
99
74
  * @param enabled - Whether the loop is active (default: true)
100
- *
101
- * @example
102
- * ```tsx
103
- * function GameContent() {
104
- * const posRef = useRef({ x: 0, y: 0 });
105
- *
106
- * useGameLoop((delta) => {
107
- * posRef.current.x += 2 * delta;
108
- * if (posRef.current.x > 256) posRef.current.x = 0;
109
- * });
110
- *
111
- * return <Sprite x={posRef.current.x} y={posRef.current.y} texture={...} />;
112
- * }
113
- * ```
114
75
  */
115
76
  export function useGameLoop(callback, enabled = true) {
116
- useTick(callback, enabled);
77
+ const context = useContext(PixiContext);
78
+ const callbackRef = useRef(callback);
79
+ callbackRef.current = callback;
80
+ useEffect(() => {
81
+ if (!context || !enabled)
82
+ return;
83
+ const wrappedCallback = (delta) => {
84
+ callbackRef.current(delta);
85
+ };
86
+ return context.addTickerCallback(wrappedCallback);
87
+ }, [context, enabled]);
88
+ }
89
+ /**
90
+ * PixiGame - Main wrapper component for pixi.js games
91
+ *
92
+ * Creates a PIXI.Application and provides it via context.
93
+ * Children can use usePixiApp() to access the app and create pixi objects.
94
+ */
95
+ export function PixiGame({ children, width = TILE_WIDTH, height = TILE_HEIGHT, background = 0x000000, options = {}, paused = false, onMount, }) {
96
+ const containerRef = useRef(null);
97
+ const appRef = useRef(null);
98
+ const tickerCallbacksRef = useRef(new Set());
99
+ const [isReady, setIsReady] = useState(false);
100
+ // Create PIXI Application
101
+ useEffect(() => {
102
+ if (!containerRef.current)
103
+ return;
104
+ const app = new PIXI.Application({
105
+ width,
106
+ height,
107
+ backgroundColor: background,
108
+ antialias: options.antialias ?? true,
109
+ resolution: options.resolution ?? (typeof window !== 'undefined' ? window.devicePixelRatio : 1),
110
+ autoDensity: options.autoDensity ?? true,
111
+ });
112
+ containerRef.current.appendChild(app.view);
113
+ appRef.current = app;
114
+ // Add master ticker callback that calls all registered callbacks
115
+ app.ticker.add((delta) => {
116
+ for (const callback of tickerCallbacksRef.current) {
117
+ callback(delta);
118
+ }
119
+ });
120
+ setIsReady(true);
121
+ onMount?.(app);
122
+ return () => {
123
+ setIsReady(false);
124
+ tickerCallbacksRef.current.clear();
125
+ app.destroy(true, { children: true, texture: true, baseTexture: true });
126
+ appRef.current = null;
127
+ };
128
+ }, []); // Only run once on mount
129
+ // Handle size changes
130
+ useEffect(() => {
131
+ if (appRef.current) {
132
+ appRef.current.renderer.resize(width, height);
133
+ }
134
+ }, [width, height]);
135
+ // Handle background color changes
136
+ useEffect(() => {
137
+ if (appRef.current) {
138
+ appRef.current.renderer.background.color = background;
139
+ }
140
+ }, [background]);
141
+ // Handle pause state
142
+ useEffect(() => {
143
+ if (appRef.current) {
144
+ if (paused) {
145
+ appRef.current.ticker.stop();
146
+ }
147
+ else {
148
+ appRef.current.ticker.start();
149
+ }
150
+ }
151
+ }, [paused]);
152
+ // Context value with app and ticker registration
153
+ const contextValue = {
154
+ app: appRef.current,
155
+ addTickerCallback: useCallback((callback) => {
156
+ tickerCallbacksRef.current.add(callback);
157
+ return () => {
158
+ tickerCallbacksRef.current.delete(callback);
159
+ };
160
+ }, []),
161
+ };
162
+ return (React.createElement("div", { ref: containerRef, style: { width, height } }, isReady && (React.createElement(PixiContext.Provider, { value: contextValue }, children))));
117
163
  }
118
164
  /**
119
165
  * useGameState - Helper for managing game state with refs
120
166
  *
121
167
  * Returns a ref and a forceUpdate function for when you need to trigger re-renders.
122
168
  * Use refs for continuous game state (position, velocity) to avoid re-render loops.
123
- *
124
- * @example
125
- * ```tsx
126
- * function GameContent() {
127
- * const [state, forceUpdate] = useGameState({
128
- * playerX: 128,
129
- * playerY: 400,
130
- * score: 0,
131
- * });
132
- *
133
- * useGameLoop((delta) => {
134
- * state.current.playerX += 1 * delta;
135
- * if (scoreChanged) {
136
- * forceUpdate(); // Only re-render when score changes
137
- * }
138
- * });
139
- *
140
- * return <Text text={`Score: ${state.current.score}`} />;
141
- * }
142
- * ```
143
169
  */
144
170
  export function useGameState(initialState) {
145
171
  const stateRef = useRef(initialState);
146
- const [, setTick] = React.useState(0);
172
+ const [, setTick] = useState(0);
147
173
  const forceUpdate = useCallback(() => {
148
174
  setTick((t) => t + 1);
149
175
  }, []);
@@ -153,23 +179,6 @@ export function useGameState(initialState) {
153
179
  * useGameInput - Simple keyboard input hook for games
154
180
  *
155
181
  * Returns a ref with currently pressed keys.
156
- * Can be used inside or outside PixiGame component.
157
- *
158
- * @example
159
- * ```tsx
160
- * function GameContent() {
161
- * const keys = useGameInput();
162
- * const posRef = useRef({ x: 128, y: 400 });
163
- *
164
- * useGameLoop((delta) => {
165
- * if (keys.current.ArrowLeft) posRef.current.x -= 5 * delta;
166
- * if (keys.current.ArrowRight) posRef.current.x += 5 * delta;
167
- * if (keys.current[' ']) jump(); // Space key
168
- * });
169
- *
170
- * return <Sprite x={posRef.current.x} y={posRef.current.y} texture={...} />;
171
- * }
172
- * ```
173
182
  */
174
183
  export function useGameInput() {
175
184
  const keysRef = useRef({});
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { SceneRendererProps } from './SceneRenderer.js';
2
+ import { type SceneRendererProps } from './SceneRenderer.js';
3
3
  /**
4
4
  * Props for SceneFromJson
5
5
  */
@@ -1 +1 @@
1
- {"version":3,"file":"SceneFromJson.d.ts","sourceRoot":"","sources":["../../src/scene/SceneFromJson.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAK5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC;IAC1E,4DAA4D;IAC5D,IAAI,EAAE,OAAO,CAAC;IACd,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CACtC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,UAAiB,EACjB,OAAO,EACP,SAAkB,EAClB,GAAG,KAAK,EACT,EAAE,kBAAkB,qBAyIpB"}
1
+ {"version":3,"file":"SceneFromJson.d.ts","sourceRoot":"","sources":["../../src/scene/SceneFromJson.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAG/D,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE5E;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC;IAC1E,4DAA4D;IAC5D,IAAI,EAAE,OAAO,CAAC;IACd,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CACtC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,UAAiB,EACjB,OAAO,EACP,SAAkB,EAClB,GAAG,KAAK,EACT,EAAE,kBAAkB,qBAoGpB"}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
- import React, { useState, useEffect, useMemo } from 'react';
2
+ import React, { useEffect, useMemo, useCallback } from 'react';
3
3
  import { validateScene } from '@thewhateverapp/scene-sdk';
4
+ import { SceneRenderer } from './SceneRenderer.js';
4
5
  /**
5
6
  * SceneFromJson - Renders a scene from a JSON object with validation
6
7
  *
@@ -16,18 +17,8 @@ import { validateScene } from '@thewhateverapp/scene-sdk';
16
17
  * ```
17
18
  */
18
19
  export function SceneFromJson({ json, showErrors = true, onEvent, container = 'tile', ...props }) {
19
- // SSR detection - only render SceneRenderer on client
20
- const [isClient, setIsClient] = useState(false);
21
- const [SceneRenderer, setSceneRenderer] = useState(null);
22
- // Load SceneRenderer dynamically on client only (pixi.js requires browser APIs)
23
- useEffect(() => {
24
- setIsClient(true);
25
- import('./SceneRenderer.js').then((mod) => {
26
- setSceneRenderer(() => mod.SceneRenderer);
27
- });
28
- }, []);
29
20
  // Wrap onEvent to forward to parent window via postMessage
30
- const wrappedOnEvent = React.useCallback((event, data) => {
21
+ const wrappedOnEvent = useCallback((event, data) => {
31
22
  // Call user's onEvent handler
32
23
  onEvent?.(event, data);
33
24
  // Forward to parent window for tile containers to handle
@@ -72,20 +63,6 @@ export function SceneFromJson({ json, showErrors = true, onEvent, container = 't
72
63
  }
73
64
  }
74
65
  }, [validationResult]);
75
- // Loading placeholder for SSR and dynamic import
76
- const loadingPlaceholder = (React.createElement("div", { style: {
77
- width: '100%',
78
- height: '100%',
79
- backgroundColor: '#0a0a1a',
80
- display: 'flex',
81
- alignItems: 'center',
82
- justifyContent: 'center',
83
- } },
84
- React.createElement("div", { style: {
85
- color: '#666',
86
- fontSize: 14,
87
- fontFamily: 'system-ui, sans-serif',
88
- } }, "Loading...")));
89
66
  // Show errors if validation failed
90
67
  if (!validationResult.valid) {
91
68
  if (showErrors) {
@@ -112,13 +89,9 @@ export function SceneFromJson({ json, showErrors = true, onEvent, container = 't
112
89
  throw new Error(`Scene validation failed: ${validationResult.errors.map((e) => e.message).join(', ')}`);
113
90
  }
114
91
  // Show warnings in console
115
- if (validationResult.warnings.length > 0 && isClient) {
92
+ if (validationResult.warnings.length > 0) {
116
93
  console.warn('Scene validation warnings:', validationResult.warnings);
117
94
  }
118
- // Wait for client-side rendering and dynamic import
119
- if (!isClient || !SceneRenderer) {
120
- return containerStyle ? React.createElement("div", { style: containerStyle }, loadingPlaceholder) : loadingPlaceholder;
121
- }
122
95
  const sceneContent = (React.createElement(SceneRenderer, { spec: json, onEvent: wrappedOnEvent, ...props }));
123
96
  return containerStyle ? React.createElement("div", { style: containerStyle }, sceneContent) : sceneContent;
124
97
  }