pixi-fusion 2.0.0 → 2.0.4

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.
Files changed (43) hide show
  1. package/README.md +3 -9
  2. package/dist/src/assets-manager/AssetsManager.js +1 -1
  3. package/dist/src/camera/Camera.js +8 -8
  4. package/dist/src/game-context/Game.context.js +12 -12
  5. package/dist/src/game-objects/usePhysicalObject.js +1 -1
  6. package/dist/src/game-objects/usePhysicalObjectFromConfig.js +1 -1
  7. package/dist/src/game-objects/useWalls.js +2 -2
  8. package/dist/src/hooks/useAnimatedSprite.js +4 -4
  9. package/dist/src/hooks/useCamera.d.ts +1 -1
  10. package/dist/src/hooks/useGame.d.ts +1 -1
  11. package/dist/src/hooks/useObject.js +9 -9
  12. package/dist/src/hooks/useSprite.js +1 -1
  13. package/dist/src/hooks/useTexture.js +1 -1
  14. package/dist/src/hooks/useTickerCallback.js +1 -1
  15. package/dist/src/hooks/useTilingSprite.js +2 -2
  16. package/dist/src/hooks/useWorld.d.ts +1 -1
  17. package/dist/src/layer/Layer.js +4 -4
  18. package/dist/src/physics/MatterPhysics.context.js +11 -11
  19. package/dist/src/physics/useCollisionEventHandler.js +1 -1
  20. package/dist/src/physics/usePhysicsEngineEventHandler.js +1 -1
  21. package/dist/src/physics/usePhysicsTickerCallback.js +1 -1
  22. package/dist/src/stage/Stage.js +2 -2
  23. package/dist/src/world/World.js +7 -6
  24. package/package.json +15 -4
  25. package/src/assets-manager/AssetsManager.tsx +13 -16
  26. package/src/camera/Camera.tsx +16 -28
  27. package/src/game-context/Game.context.tsx +12 -12
  28. package/src/game-objects/usePhysicalObject.ts +1 -1
  29. package/src/game-objects/usePhysicalObjectFromConfig.ts +1 -1
  30. package/src/game-objects/useWalls.ts +2 -2
  31. package/src/hooks/useAnimatedSprite.ts +4 -4
  32. package/src/hooks/useObject.ts +9 -9
  33. package/src/hooks/useSprite.ts +1 -1
  34. package/src/hooks/useTexture.ts +1 -1
  35. package/src/hooks/useTickerCallback.ts +1 -1
  36. package/src/hooks/useTilingSprite.ts +2 -2
  37. package/src/layer/Layer.tsx +4 -4
  38. package/src/physics/MatterPhysics.context.tsx +23 -17
  39. package/src/physics/useCollisionEventHandler.ts +1 -1
  40. package/src/physics/usePhysicsEngineEventHandler.ts +1 -1
  41. package/src/physics/usePhysicsTickerCallback.ts +1 -1
  42. package/src/stage/Stage.tsx +6 -12
  43. package/src/world/World.tsx +7 -6
package/README.md CHANGED
@@ -14,14 +14,14 @@ npm i --save pixi-fusion
14
14
 
15
15
  # Basic Usage Example
16
16
 
17
- 1. Create a component with `GameContextProvider` and `World`.
17
+ 1. Create a component with `GameContextProvider` and `Stage`.
18
18
 
19
19
  ```
20
20
  <GameContextProvider timeout={10}>
21
21
  <World size={{width: 300, height: 300}} eventMode="dynamic">
22
- <layer>
22
+ <Layer>
23
23
  <MyAwesomeStage />
24
- </Layer>
24
+ </layer>
25
25
  </World>
26
26
  </GameContextProvider>
27
27
  ```
@@ -76,12 +76,6 @@ export const MyAwesomeStage: React.FC<MazeHeroProps> = () => {
76
76
  };
77
77
  ```
78
78
 
79
- # Demos
80
-
81
- - [Maze](https://laverve.github.io/fusion/?path=/story/games-maze-gameplay--maze-story)
82
- - [WordSearch](https://laverve.github.io/fusion/?path=/story/games-wordsearch-gameplay--word-search-story)
83
- - [Spelling spree](https://laverve.github.io/fusion/?path=/story/games-spelling-spree-gameplay--spelling-spree)
84
-
85
79
  # Contribution guidelines
86
80
 
87
81
  You are encouraged to contribute to this project as soon as you see any defects or issues.
@@ -23,7 +23,7 @@ export const AssetsManager = ({ children }) => {
23
23
  finally {
24
24
  setIsFetching(false);
25
25
  }
26
- }, [isFetching, isError, isFetched, error]);
26
+ }, []);
27
27
  const unload = useCallback((groupId) => {
28
28
  Assets.unloadBundle(groupId);
29
29
  }, []);
@@ -22,10 +22,10 @@ export const Camera = ({ children, clampZoom }) => {
22
22
  : null, [size, application]);
23
23
  const addObject = useCallback((thing) => {
24
24
  setThings((oldThings) => [...oldThings, thing]);
25
- }, [things]);
25
+ }, []);
26
26
  const removeObject = useCallback((thing) => {
27
27
  setThings((oldThings) => oldThings.filter(({ uid }) => uid === thing.uid));
28
- }, [application]);
28
+ }, []);
29
29
  const conextValue = useMemo(() => ({
30
30
  addObject,
31
31
  removeObject
@@ -34,7 +34,7 @@ export const Camera = ({ children, clampZoom }) => {
34
34
  if (viewport && clampZoom && application) {
35
35
  viewport.clampZoom(clampZoom);
36
36
  }
37
- }, [clampZoom]);
37
+ }, [clampZoom, application, viewport]);
38
38
  useEffect(() => {
39
39
  if (viewport) {
40
40
  if (size.width) {
@@ -44,7 +44,7 @@ export const Camera = ({ children, clampZoom }) => {
44
44
  viewport.height = size.height;
45
45
  }
46
46
  }
47
- }, [size, viewport?.uid, application]);
47
+ }, [size, viewport, application]);
48
48
  useEffect(() => {
49
49
  if (!application || !viewport) {
50
50
  return;
@@ -61,7 +61,7 @@ export const Camera = ({ children, clampZoom }) => {
61
61
  removeObjectFromStage(viewport);
62
62
  setIsReady(false);
63
63
  };
64
- }, [viewport?.uid]);
64
+ }, [viewport, addObjectToStage, removeObjectFromStage]);
65
65
  useEffect(() => {
66
66
  if (!viewport || !isReady) {
67
67
  return;
@@ -83,16 +83,16 @@ export const Camera = ({ children, clampZoom }) => {
83
83
  }, [isReady, viewport, ensureVisibleOptions]);
84
84
  const followCallback = useCallback((container) => {
85
85
  setFollowContainer(container);
86
- }, [followContainer]);
86
+ }, []);
87
87
  const ensureVisibleCallback = useCallback((options) => {
88
88
  setEnsureVisibleOptions(options);
89
- }, [followContainer]);
89
+ }, []);
90
90
  const value = useMemo(() => {
91
91
  return {
92
92
  follow: followCallback,
93
93
  ensureVisible: ensureVisibleCallback
94
94
  };
95
- }, [followCallback]);
95
+ }, [followCallback, ensureVisibleCallback]);
96
96
  return (React.createElement(CameraContext.Provider, { value: value },
97
97
  React.createElement(LayerContext.Provider, { value: conextValue },
98
98
  React.createElement(Layer, null, children))));
@@ -1,4 +1,4 @@
1
- import React, { createContext, useMemo, useRef, useState } from "react";
1
+ import React, { createContext, useCallback, useMemo, useRef, useState } from "react";
2
2
  import { GameStatus } from "../types";
3
3
  export const GameContext = createContext({
4
4
  reset: () => { },
@@ -19,7 +19,7 @@ export const GameContextProvider = ({ children, timeout: inputTimeout = 0, event
19
19
  const [endTime, setEndTime] = useState(null);
20
20
  const [pauseTime, setPauseTime] = useState(0);
21
21
  const [pausedAtTime, setPausedAtTime] = useState(null);
22
- const onTimedOut = () => {
22
+ const onTimedOut = useCallback(() => {
23
23
  const newEndTime = +new Date();
24
24
  const newStatus = GameStatus.TIMEDOUT;
25
25
  setEndTime(newEndTime);
@@ -31,8 +31,8 @@ export const GameContextProvider = ({ children, timeout: inputTimeout = 0, event
31
31
  pausedAtTime: null,
32
32
  pauseTime
33
33
  });
34
- };
35
- const start = () => {
34
+ }, [events, pauseTime, startTime]);
35
+ const start = useCallback(() => {
36
36
  const newStartTime = startTime || +new Date();
37
37
  const newStatus = GameStatus.IN_PROGRESS;
38
38
  const newEndTime = null;
@@ -54,8 +54,8 @@ export const GameContextProvider = ({ children, timeout: inputTimeout = 0, event
54
54
  pausedAtTime: null,
55
55
  pauseTime: newPauseTime
56
56
  });
57
- };
58
- const reset = () => {
57
+ }, [events, onTimedOut, pauseTime, pausedAtTime, startTime, timeout]);
58
+ const reset = useCallback(() => {
59
59
  const newStartTime = null;
60
60
  const newStatus = GameStatus.READY;
61
61
  const newEndTime = null;
@@ -76,8 +76,8 @@ export const GameContextProvider = ({ children, timeout: inputTimeout = 0, event
76
76
  pausedAtTime: newPausedAtTime,
77
77
  pauseTime: newPauseTime
78
78
  });
79
- };
80
- const stop = () => {
79
+ }, [events]);
80
+ const stop = useCallback(() => {
81
81
  const newStatus = GameStatus.COMPLETED;
82
82
  const newEndTime = +new Date();
83
83
  if (timerRef.current) {
@@ -92,8 +92,8 @@ export const GameContextProvider = ({ children, timeout: inputTimeout = 0, event
92
92
  pausedAtTime,
93
93
  pauseTime
94
94
  });
95
- };
96
- const pause = () => {
95
+ }, [events, pauseTime, pausedAtTime, startTime]);
96
+ const pause = useCallback(() => {
97
97
  const newStatus = GameStatus.PAUSED;
98
98
  const newPausedAtTime = +new Date();
99
99
  if (timerRef.current) {
@@ -108,7 +108,7 @@ export const GameContextProvider = ({ children, timeout: inputTimeout = 0, event
108
108
  status: newStatus,
109
109
  pauseTime
110
110
  });
111
- };
111
+ }, [events, pauseTime, startTime]);
112
112
  const contextValue = useMemo(() => ({
113
113
  status,
114
114
  startTime,
@@ -119,6 +119,6 @@ export const GameContextProvider = ({ children, timeout: inputTimeout = 0, event
119
119
  start,
120
120
  reset,
121
121
  pause
122
- }), [timeout, status, startTime, endTime, pauseTime, pausedAtTime]);
122
+ }), [timeout, status, startTime, endTime, pauseTime, pause, reset, start, stop]);
123
123
  return React.createElement(GameContext.Provider, { value: contextValue }, children);
124
124
  };
@@ -10,7 +10,7 @@ export const usePhysicalObject = ({ physicalObject }) => {
10
10
  return () => {
11
11
  removeBody(physicalObject);
12
12
  };
13
- }, [physicalObject?.id]);
13
+ }, [physicalObject, addBody, removeBody]);
14
14
  return {
15
15
  physicalObject
16
16
  };
@@ -7,7 +7,7 @@ export const usePhysicalObjectFromConfig = ({ position = { x: 0, y: 0 }, ...phys
7
7
  return Body.create({ position, ...physicalObjectConfig });
8
8
  }
9
9
  return null;
10
- }, []);
10
+ }, [physicalObjectConfig, position]);
11
11
  usePhysicalObject({ physicalObject });
12
12
  return { physicalObject };
13
13
  };
@@ -49,11 +49,11 @@ export const useWalls = ({ left = true, right = true, top = true, bottom = true
49
49
  }
50
50
  Composite.add(walls, parts);
51
51
  return walls;
52
- }, [width, height]);
52
+ }, [width, height, bottom, center.x, center.y, left, right, top]);
53
53
  useEffect(() => {
54
54
  addBody(body);
55
55
  return () => {
56
56
  removeBody(body);
57
57
  };
58
- }, [body.id]);
58
+ }, [body, addBody, removeBody]);
59
59
  };
@@ -14,19 +14,19 @@ export const useAnimatedSprite = ({ texture, spritesheet: spritesheetJSON, anima
14
14
  return sheet;
15
15
  }
16
16
  return null;
17
- }, [spritesheetJSON]);
17
+ }, [spritesheetJSON, isFetched, textures]);
18
18
  const sprite = useMemo(() => {
19
19
  if (spritesheet?.animations?.[animation]) {
20
20
  return new AnimatedSprite({ textures: spritesheet?.animations?.[animation] });
21
21
  }
22
22
  return null;
23
- }, [texture, spritesheetJSON, animation]);
23
+ }, [animation, spritesheet?.animations]);
24
24
  useObject({ object: sprite, ...options });
25
25
  useEffect(() => {
26
26
  if (sprite) {
27
27
  sprite.animationSpeed = animationSpeed;
28
28
  }
29
- }, [animationSpeed, sprite?.uid]);
29
+ }, [animationSpeed, sprite]);
30
30
  useEffect(() => {
31
31
  if (sprite) {
32
32
  if (isPlaying) {
@@ -36,6 +36,6 @@ export const useAnimatedSprite = ({ texture, spritesheet: spritesheetJSON, anima
36
36
  sprite.stop();
37
37
  }
38
38
  }
39
- }, [isPlaying, sprite?.uid]);
39
+ }, [isPlaying, sprite]);
40
40
  return sprite;
41
41
  };
@@ -1 +1 @@
1
- export declare const useCamera: () => import("../camera").CameraContextValue;
1
+ export declare const useCamera: () => import("..").CameraContextValue;
@@ -1 +1 @@
1
- export declare const useGame: () => import("../game-context").GameContextValue;
1
+ export declare const useGame: () => import("..").GameContextValue;
@@ -10,45 +10,45 @@ export const useObject = ({ object, anchor, position, skew, scale, width, angle,
10
10
  return () => {
11
11
  removeObject(object);
12
12
  };
13
- }, [object?.uid]);
13
+ }, [object, addObject, removeObject]);
14
14
  useEffect(() => {
15
15
  if (anchor !== undefined && object) {
16
16
  object.anchor = anchor;
17
17
  }
18
- }, [anchor, object?.uid]);
18
+ }, [anchor, object]);
19
19
  useEffect(() => {
20
20
  if (angle !== undefined && object) {
21
21
  object.angle = angle;
22
22
  }
23
- }, [angle, object?.uid]);
23
+ }, [angle, object]);
24
24
  useEffect(() => {
25
25
  if (object && position !== undefined) {
26
26
  object.position = position;
27
27
  }
28
- }, [position, object?.uid]);
28
+ }, [position, object]);
29
29
  useEffect(() => {
30
30
  if (object && skew !== undefined) {
31
31
  object.skew = skew;
32
32
  }
33
- }, [skew, object?.uid]);
33
+ }, [skew, object]);
34
34
  useEffect(() => {
35
35
  if (object && alpha !== undefined) {
36
36
  object.alpha = alpha;
37
37
  }
38
- }, [alpha, object?.uid]);
38
+ }, [alpha, object]);
39
39
  useEffect(() => {
40
40
  if (object && scale !== undefined) {
41
41
  object.scale = scale;
42
42
  }
43
- }, [scale, object?.uid]);
43
+ }, [scale, object]);
44
44
  useEffect(() => {
45
45
  if (object) {
46
46
  object.visible = visible;
47
47
  }
48
- }, [visible, object?.uid]);
48
+ }, [visible, object]);
49
49
  useEffect(() => {
50
50
  if (object && width !== undefined) {
51
51
  object.width = width;
52
52
  }
53
- }, [width, object?.uid]);
53
+ }, [width, object]);
54
54
  };
@@ -15,7 +15,7 @@ export const useSprite = ({ texture = "", ...options }) => {
15
15
  return Sprite.from(texture);
16
16
  }
17
17
  return new Sprite({});
18
- }, [texture, frames, isFetched]);
18
+ }, [texture, isFetched]);
19
19
  useObject({ object: sprite, ...options });
20
20
  return sprite;
21
21
  };
@@ -4,7 +4,7 @@ export const useTextures = ({ keys = [] }) => {
4
4
  const { getAsset, isFetched, isFetching, isError } = useAssetManager();
5
5
  const textures = useMemo(() => {
6
6
  return isFetched ? keys.map((key) => getAsset(key)) : [];
7
- }, [keys, isFetched, isFetching, isError]);
7
+ }, [keys, isFetched, getAsset]);
8
8
  return useMemo(() => ({
9
9
  textures: textures,
10
10
  isFetched,
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from "react";
2
- import { useWorld } from "../hooks";
2
+ import { useWorld } from ".";
3
3
  export const useTickerCallback = ({ isEnabled = true, callback }) => {
4
4
  const { application } = useWorld();
5
5
  useEffect(() => {
@@ -15,12 +15,12 @@ export const useTilingSprite = ({ texture = "", tilePosition, ...options }) => {
15
15
  return TilingSprite.from(texture);
16
16
  }
17
17
  return new TilingSprite({});
18
- }, [texture, frames]);
18
+ }, [texture, isFetched]);
19
19
  useObject({ object: sprite, ...options });
20
20
  useEffect(() => {
21
21
  if (tilePosition && sprite) {
22
22
  sprite.tilePosition = tilePosition;
23
23
  }
24
- }, [tilePosition, sprite?.uid]);
24
+ }, [tilePosition, sprite]);
25
25
  return sprite;
26
26
  };
@@ -1 +1 @@
1
- export declare const useWorld: () => import("../world/World.context").WorldContextValue;
1
+ export declare const useWorld: () => import("..").WorldContextValue;
@@ -19,16 +19,16 @@ export const Layer = ({ options, children }) => {
19
19
  return () => {
20
20
  removeObjectFromStaeg(container);
21
21
  };
22
- }, [container?.uid]);
22
+ }, [container, addObjectIntoParent, addObjectIntoStage, removeObjectFromParent, removeObjectFromStaeg]);
23
23
  const addObject = useCallback((thing) => {
24
24
  container.addChild(thing);
25
- }, [container?.uid]);
25
+ }, [container]);
26
26
  const removeObject = useCallback((thing) => {
27
27
  container.removeChild(thing);
28
- }, [container?.uid]);
28
+ }, [container]);
29
29
  const conextValue = useMemo(() => ({
30
30
  addObject,
31
31
  removeObject
32
- }), [container?.uid]);
32
+ }), [addObject, removeObject]);
33
33
  return React.createElement(LayerContext.Provider, { value: conextValue }, children);
34
34
  };
@@ -1,4 +1,4 @@
1
- import React, { createContext, useEffect, useMemo, useRef, useState } from "react";
1
+ import React, { createContext, useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { Engine, Runner, World } from "matter-js";
3
3
  export const MatterPhysicsContext = createContext({
4
4
  config: {
@@ -31,28 +31,28 @@ export const MatterPhysicsContextProvider = ({ isRunning = false, children, conf
31
31
  });
32
32
  }, [world]);
33
33
  const runner = useMemo(() => Runner.create(), []);
34
- const addBody = (body) => {
34
+ const addBody = useCallback((body) => {
35
35
  if (engine?.world) {
36
36
  World.add(engine?.world, body);
37
37
  }
38
- };
39
- const removeBody = (body) => {
38
+ }, [engine]);
39
+ const removeBody = useCallback((body) => {
40
40
  if (engine?.world) {
41
41
  World.remove(engine?.world, body);
42
42
  }
43
- };
44
- const run = () => {
43
+ }, [engine]);
44
+ const run = useCallback(() => {
45
45
  if (!isRunningRef.current) {
46
46
  Runner.run(runner, engine);
47
47
  isRunningRef.current = true;
48
48
  }
49
- };
50
- const stop = () => {
49
+ }, [runner, engine]);
50
+ const stop = useCallback(() => {
51
51
  if (isRunningRef.current) {
52
52
  Runner.stop(runner);
53
53
  isRunningRef.current = false;
54
54
  }
55
- };
55
+ }, [runner]);
56
56
  const conextValue = useMemo(() => ({
57
57
  config: localConfig,
58
58
  runner,
@@ -61,7 +61,7 @@ export const MatterPhysicsContextProvider = ({ isRunning = false, children, conf
61
61
  stop,
62
62
  addBody,
63
63
  removeBody
64
- }), [runner, engine, localConfig]);
64
+ }), [runner, engine, localConfig, addBody, removeBody, run, stop]);
65
65
  useEffect(() => {
66
66
  if (isRunning) {
67
67
  run();
@@ -69,6 +69,6 @@ export const MatterPhysicsContextProvider = ({ isRunning = false, children, conf
69
69
  return () => {
70
70
  stop();
71
71
  };
72
- }, [isRunning]);
72
+ }, [isRunning, run, stop]);
73
73
  return React.createElement(MatterPhysicsContext.Provider, { value: conextValue }, children);
74
74
  };
@@ -11,5 +11,5 @@ export const useCollisionEventHandler = ({ isEnabled = true, event, callback })
11
11
  return () => {
12
12
  Events.off(engine, event, callback);
13
13
  };
14
- }, [isEnabled, callback, event]);
14
+ }, [isEnabled, callback, event, engine]);
15
15
  };
@@ -11,5 +11,5 @@ export const usePhysicsEngineEventHandler = ({ isEnabled = true, event, callback
11
11
  return () => {
12
12
  Events.off(engine, event, callback);
13
13
  };
14
- }, [isEnabled, event, callback]);
14
+ }, [isEnabled, event, callback, engine]);
15
15
  };
@@ -11,5 +11,5 @@ export const usePhysicsTickerCallback = ({ isEnabled = true, callback }) => {
11
11
  return () => {
12
12
  Events.off(runner, "tick", callback);
13
13
  };
14
- }, [isEnabled, callback]);
14
+ }, [isEnabled, callback, runner]);
15
15
  };
@@ -7,10 +7,10 @@ export const Stage = ({ children }) => {
7
7
  const [things, setThings] = useState([]);
8
8
  const addObject = useCallback((thing) => {
9
9
  setThings((oldThings) => [...oldThings, thing]);
10
- }, [things]);
10
+ }, []);
11
11
  const removeObject = useCallback((thing) => {
12
12
  setThings((oldThings) => oldThings.filter(({ uid }) => uid === thing.uid));
13
- }, [application]);
13
+ }, []);
14
14
  const conextValue = useMemo(() => ({
15
15
  addObject,
16
16
  removeObject
@@ -27,7 +27,7 @@ export const World = ({ children, eventMode, size }) => {
27
27
  width: applicationRef.screen.width,
28
28
  height: applicationRef.screen.height
29
29
  };
30
- }, [size, canvasRef.current, applicationRef]);
30
+ }, [size, canvasRef, applicationRef]);
31
31
  useEffect(() => {
32
32
  if (!applicationRef) {
33
33
  const application = new Application();
@@ -35,13 +35,13 @@ export const World = ({ children, eventMode, size }) => {
35
35
  setApplicationRef(application);
36
36
  });
37
37
  }
38
- }, []);
38
+ }, [applicationRef]);
39
39
  useEffect(() => {
40
40
  if (applicationRef && canvasRef.current) {
41
41
  applicationRef.resizeTo = canvasRef.current;
42
42
  applicationRef.resize();
43
43
  }
44
- }, [canvasRef.current, applicationRef]);
44
+ }, [canvasRef, applicationRef]);
45
45
  const conextValue = useMemo(() => ({
46
46
  application: applicationRef,
47
47
  size: worldSize
@@ -50,13 +50,14 @@ export const World = ({ children, eventMode, size }) => {
50
50
  if (!applicationRef) {
51
51
  return;
52
52
  }
53
- canvasRef.current?.appendChild(applicationRef.canvas);
53
+ const canvas = canvasRef.current;
54
+ canvas?.appendChild(applicationRef.canvas);
54
55
  return () => {
55
56
  if (applicationRef) {
56
- canvasRef.current?.removeChild(applicationRef.canvas);
57
+ canvas?.removeChild(applicationRef.canvas);
57
58
  }
58
59
  };
59
- }, [canvasRef.current, applicationRef]);
60
+ }, [canvasRef, applicationRef]);
60
61
  useEffect(() => {
61
62
  if (applicationRef) {
62
63
  applicationRef.stage.eventMode = eventMode;
package/package.json CHANGED
@@ -5,14 +5,23 @@
5
5
  "url": "https://github.com/laverve/fusion/issues"
6
6
  },
7
7
  "dependencies": {
8
- "@types/matter-js": "^0.19.8",
8
+ "@types/matter-js": "^0.20.0",
9
9
  "matter-js": "^0.20.0",
10
- "pixi.js": "^8.6.6",
10
+ "pixi.js": "^8.8.0",
11
11
  "pixi-viewport": "^6.0.3"
12
12
  },
13
13
  "peerDependencies": {
14
14
  "react": "^19.0.0"
15
15
  },
16
+ "devDependencies": {
17
+ "@laverve/eslint-utils": "^5.1.11",
18
+ "@laverve/test-utils": "^5.1.11",
19
+ "@types/react": "^19.0.10",
20
+ "husky": "9.1.7",
21
+ "lint-staged": "^16.0.0",
22
+ "ts-node": "^10.9.1",
23
+ "typescript": "^5.7.3"
24
+ },
16
25
  "description": "This module offers a set of common components needed for playing games.",
17
26
  "keywords": [
18
27
  "game",
@@ -41,12 +50,14 @@
41
50
  "url": "https://github.com/laverve/fusion.git"
42
51
  },
43
52
  "scripts": {
44
- "lint": "eslint .",
53
+ "lint": "eslint ./src/",
54
+ "lint:fix": "eslint ./src/ --fix",
45
55
  "lint:staged": "lint-staged",
56
+ "prepare": "husky",
46
57
  "test": "jest --passWithNoTests",
47
58
  "build": "tsc",
48
59
  "build:dev": "tsc -w"
49
60
  },
50
- "version": "2.0.0",
61
+ "version": "2.0.4",
51
62
  "webpack": "./src/index.ts"
52
63
  }
@@ -12,22 +12,19 @@ export const AssetsManager: React.FC<PropsWithChildren> = ({ children }) => {
12
12
  Assets.cache.reset();
13
13
  }, []);
14
14
 
15
- const load = useCallback(
16
- async (groupId: string, localAsset: Asset[]) => {
17
- try {
18
- Assets.addBundle(groupId, localAsset);
19
- await Assets.loadBundle(groupId);
20
- setIsFetched(true);
21
- } catch (e) {
22
- setIsError(true);
23
- setIsFetched(false);
24
- setError(e);
25
- } finally {
26
- setIsFetching(false);
27
- }
28
- },
29
- [isFetching, isError, isFetched, error]
30
- );
15
+ const load = useCallback(async (groupId: string, localAsset: Asset[]) => {
16
+ try {
17
+ Assets.addBundle(groupId, localAsset);
18
+ await Assets.loadBundle(groupId);
19
+ setIsFetched(true);
20
+ } catch (e) {
21
+ setIsError(true);
22
+ setIsFetched(false);
23
+ setError(e);
24
+ } finally {
25
+ setIsFetching(false);
26
+ }
27
+ }, []);
31
28
 
32
29
  const unload = useCallback((groupId: string) => {
33
30
  Assets.unloadBundle(groupId);
@@ -32,19 +32,13 @@ export const Camera: React.FC<CameraProps & PropsWithChildren> = ({ children, cl
32
32
  [size, application]
33
33
  );
34
34
 
35
- const addObject = useCallback(
36
- (thing: Container) => {
37
- setThings((oldThings) => [...oldThings, thing]);
38
- },
39
- [things]
40
- );
35
+ const addObject = useCallback((thing: Container) => {
36
+ setThings((oldThings) => [...oldThings, thing]);
37
+ }, []);
41
38
 
42
- const removeObject = useCallback(
43
- (thing: Container) => {
44
- setThings((oldThings) => oldThings.filter(({ uid }) => uid === thing.uid));
45
- },
46
- [application]
47
- );
39
+ const removeObject = useCallback((thing: Container) => {
40
+ setThings((oldThings) => oldThings.filter(({ uid }) => uid === thing.uid));
41
+ }, []);
48
42
 
49
43
  const conextValue = useMemo<StageContextValue>(
50
44
  () => ({
@@ -58,7 +52,7 @@ export const Camera: React.FC<CameraProps & PropsWithChildren> = ({ children, cl
58
52
  if (viewport && clampZoom && application) {
59
53
  viewport.clampZoom(clampZoom);
60
54
  }
61
- }, [clampZoom]);
55
+ }, [clampZoom, application, viewport]);
62
56
 
63
57
  useEffect(() => {
64
58
  if (viewport) {
@@ -69,7 +63,7 @@ export const Camera: React.FC<CameraProps & PropsWithChildren> = ({ children, cl
69
63
  viewport.height = size.height;
70
64
  }
71
65
  }
72
- }, [size, viewport?.uid, application]);
66
+ }, [size, viewport, application]);
73
67
 
74
68
  useEffect(() => {
75
69
  if (!application || !viewport) {
@@ -90,7 +84,7 @@ export const Camera: React.FC<CameraProps & PropsWithChildren> = ({ children, cl
90
84
  removeObjectFromStage(viewport);
91
85
  setIsReady(false);
92
86
  };
93
- }, [viewport?.uid]);
87
+ }, [viewport, addObjectToStage, removeObjectFromStage]);
94
88
 
95
89
  useEffect(() => {
96
90
  if (!viewport || !isReady) {
@@ -122,26 +116,20 @@ export const Camera: React.FC<CameraProps & PropsWithChildren> = ({ children, cl
122
116
  }
123
117
  }, [isReady, viewport, ensureVisibleOptions]);
124
118
 
125
- const followCallback = useCallback(
126
- (container: Container) => {
127
- setFollowContainer(container);
128
- },
129
- [followContainer]
130
- );
119
+ const followCallback = useCallback((container: Container) => {
120
+ setFollowContainer(container);
121
+ }, []);
131
122
 
132
- const ensureVisibleCallback = useCallback(
133
- (options: EnsureVisibleOptions) => {
134
- setEnsureVisibleOptions(options);
135
- },
136
- [followContainer]
137
- );
123
+ const ensureVisibleCallback = useCallback((options: EnsureVisibleOptions) => {
124
+ setEnsureVisibleOptions(options);
125
+ }, []);
138
126
 
139
127
  const value = useMemo(() => {
140
128
  return {
141
129
  follow: followCallback,
142
130
  ensureVisible: ensureVisibleCallback
143
131
  };
144
- }, [followCallback]);
132
+ }, [followCallback, ensureVisibleCallback]);
145
133
 
146
134
  return (
147
135
  <CameraContext.Provider value={value}>
@@ -1,4 +1,4 @@
1
- import React, { PropsWithChildren, createContext, useMemo, useRef, useState } from "react";
1
+ import React, { PropsWithChildren, createContext, useCallback, useMemo, useRef, useState } from "react";
2
2
  import { GameStatus, GameStatusChangeEvent } from "../types";
3
3
 
4
4
  export type GameContextValue = {
@@ -52,7 +52,7 @@ export const GameContextProvider: React.FC<GameContextProviderProps> = ({
52
52
  const [pauseTime, setPauseTime] = useState<number>(0);
53
53
  const [pausedAtTime, setPausedAtTime] = useState<number | null>(null);
54
54
 
55
- const onTimedOut = () => {
55
+ const onTimedOut = useCallback(() => {
56
56
  const newEndTime = +new Date();
57
57
  const newStatus = GameStatus.TIMEDOUT;
58
58
 
@@ -66,9 +66,9 @@ export const GameContextProvider: React.FC<GameContextProviderProps> = ({
66
66
  pausedAtTime: null,
67
67
  pauseTime
68
68
  });
69
- };
69
+ }, [events, pauseTime, startTime]);
70
70
 
71
- const start = () => {
71
+ const start = useCallback(() => {
72
72
  const newStartTime = startTime || +new Date();
73
73
  const newStatus = GameStatus.IN_PROGRESS;
74
74
  const newEndTime = null;
@@ -93,9 +93,9 @@ export const GameContextProvider: React.FC<GameContextProviderProps> = ({
93
93
  pausedAtTime: null,
94
94
  pauseTime: newPauseTime
95
95
  });
96
- };
96
+ }, [events, onTimedOut, pauseTime, pausedAtTime, startTime, timeout]);
97
97
 
98
- const reset = () => {
98
+ const reset = useCallback(() => {
99
99
  const newStartTime = null;
100
100
  const newStatus = GameStatus.READY;
101
101
  const newEndTime = null;
@@ -119,9 +119,9 @@ export const GameContextProvider: React.FC<GameContextProviderProps> = ({
119
119
  pausedAtTime: newPausedAtTime,
120
120
  pauseTime: newPauseTime
121
121
  });
122
- };
122
+ }, [events]);
123
123
 
124
- const stop = () => {
124
+ const stop = useCallback(() => {
125
125
  const newStatus = GameStatus.COMPLETED;
126
126
  const newEndTime = +new Date();
127
127
 
@@ -138,9 +138,9 @@ export const GameContextProvider: React.FC<GameContextProviderProps> = ({
138
138
  pausedAtTime,
139
139
  pauseTime
140
140
  });
141
- };
141
+ }, [events, pauseTime, pausedAtTime, startTime]);
142
142
 
143
- const pause = () => {
143
+ const pause = useCallback(() => {
144
144
  const newStatus = GameStatus.PAUSED;
145
145
  const newPausedAtTime = +new Date();
146
146
 
@@ -157,7 +157,7 @@ export const GameContextProvider: React.FC<GameContextProviderProps> = ({
157
157
  status: newStatus,
158
158
  pauseTime
159
159
  });
160
- };
160
+ }, [events, pauseTime, startTime]);
161
161
 
162
162
  const contextValue = useMemo(
163
163
  () => ({
@@ -171,7 +171,7 @@ export const GameContextProvider: React.FC<GameContextProviderProps> = ({
171
171
  reset,
172
172
  pause
173
173
  }),
174
- [timeout, status, startTime, endTime, pauseTime, pausedAtTime]
174
+ [timeout, status, startTime, endTime, pauseTime, pause, reset, start, stop]
175
175
  );
176
176
 
177
177
  return <GameContext.Provider value={contextValue}>{children}</GameContext.Provider>;
@@ -16,7 +16,7 @@ export const usePhysicalObject = ({ physicalObject }: { physicalObject: Nullable
16
16
  return () => {
17
17
  removeBody(physicalObject);
18
18
  };
19
- }, [physicalObject?.id]);
19
+ }, [physicalObject, addBody, removeBody]);
20
20
 
21
21
  return {
22
22
  physicalObject
@@ -14,7 +14,7 @@ export const usePhysicalObjectFromConfig = <PhysicalObjectType extends Nullable<
14
14
  }
15
15
 
16
16
  return null;
17
- }, []) as PhysicalObjectType;
17
+ }, [physicalObjectConfig, position]) as PhysicalObjectType;
18
18
 
19
19
  usePhysicalObject({ physicalObject });
20
20
 
@@ -81,7 +81,7 @@ export const useWalls = ({ left = true, right = true, top = true, bottom = true
81
81
  Composite.add(walls, parts);
82
82
 
83
83
  return walls;
84
- }, [width, height]);
84
+ }, [width, height, bottom, center.x, center.y, left, right, top]);
85
85
 
86
86
  useEffect(() => {
87
87
  addBody(body);
@@ -89,5 +89,5 @@ export const useWalls = ({ left = true, right = true, top = true, bottom = true
89
89
  return () => {
90
90
  removeBody(body);
91
91
  };
92
- }, [body.id]);
92
+ }, [body, addBody, removeBody]);
93
93
  };
@@ -33,7 +33,7 @@ export const useAnimatedSprite = ({
33
33
  return sheet;
34
34
  }
35
35
  return null;
36
- }, [spritesheetJSON]);
36
+ }, [spritesheetJSON, isFetched, textures]);
37
37
 
38
38
  const sprite = useMemo(() => {
39
39
  if (spritesheet?.animations?.[animation]) {
@@ -41,7 +41,7 @@ export const useAnimatedSprite = ({
41
41
  }
42
42
 
43
43
  return null;
44
- }, [texture, spritesheetJSON, animation]);
44
+ }, [animation, spritesheet?.animations]);
45
45
 
46
46
  useObject({ object: sprite, ...options });
47
47
 
@@ -49,7 +49,7 @@ export const useAnimatedSprite = ({
49
49
  if (sprite) {
50
50
  sprite.animationSpeed = animationSpeed;
51
51
  }
52
- }, [animationSpeed, sprite?.uid]);
52
+ }, [animationSpeed, sprite]);
53
53
 
54
54
  useEffect(() => {
55
55
  if (sprite) {
@@ -59,7 +59,7 @@ export const useAnimatedSprite = ({
59
59
  sprite.stop();
60
60
  }
61
61
  }
62
- }, [isPlaying, sprite?.uid]);
62
+ }, [isPlaying, sprite]);
63
63
 
64
64
  return sprite;
65
65
  };
@@ -29,53 +29,53 @@ export const useObject = ({
29
29
  return () => {
30
30
  removeObject(object);
31
31
  };
32
- }, [object?.uid]);
32
+ }, [object, addObject, removeObject]);
33
33
 
34
34
  useEffect(() => {
35
35
  if (anchor !== undefined && object) {
36
36
  object.anchor = anchor;
37
37
  }
38
- }, [anchor, object?.uid]);
38
+ }, [anchor, object]);
39
39
 
40
40
  useEffect(() => {
41
41
  if (angle !== undefined && object) {
42
42
  object.angle = angle;
43
43
  }
44
- }, [angle, object?.uid]);
44
+ }, [angle, object]);
45
45
 
46
46
  useEffect(() => {
47
47
  if (object && position !== undefined) {
48
48
  object.position = position;
49
49
  }
50
- }, [position, object?.uid]);
50
+ }, [position, object]);
51
51
 
52
52
  useEffect(() => {
53
53
  if (object && skew !== undefined) {
54
54
  object.skew = skew;
55
55
  }
56
- }, [skew, object?.uid]);
56
+ }, [skew, object]);
57
57
 
58
58
  useEffect(() => {
59
59
  if (object && alpha !== undefined) {
60
60
  object.alpha = alpha;
61
61
  }
62
- }, [alpha, object?.uid]);
62
+ }, [alpha, object]);
63
63
 
64
64
  useEffect(() => {
65
65
  if (object && scale !== undefined) {
66
66
  object.scale = scale;
67
67
  }
68
- }, [scale, object?.uid]);
68
+ }, [scale, object]);
69
69
 
70
70
  useEffect(() => {
71
71
  if (object) {
72
72
  object.visible = visible;
73
73
  }
74
- }, [visible, object?.uid]);
74
+ }, [visible, object]);
75
75
 
76
76
  useEffect(() => {
77
77
  if (object && width !== undefined) {
78
78
  object.width = width;
79
79
  }
80
- }, [width, object?.uid]);
80
+ }, [width, object]);
81
81
  };
@@ -25,7 +25,7 @@ export const useSprite = ({ texture = "", ...options }: UseSpriteOptions) => {
25
25
  }
26
26
 
27
27
  return new Sprite({});
28
- }, [texture, frames, isFetched]);
28
+ }, [texture, isFetched]);
29
29
 
30
30
  useObject({ object: sprite, ...options });
31
31
 
@@ -8,7 +8,7 @@ export const useTextures = ({ keys = [] }: { keys?: string[] }) => {
8
8
 
9
9
  const textures = useMemo(() => {
10
10
  return isFetched ? keys.map((key) => getAsset(key) as Texture) : [];
11
- }, [keys, isFetched, isFetching, isError]);
11
+ }, [keys, isFetched, getAsset]);
12
12
 
13
13
  return useMemo(
14
14
  () => ({
@@ -1,7 +1,7 @@
1
1
  import { useEffect } from "react";
2
2
  import { TickerCallback } from "pixi.js";
3
3
 
4
- import { useWorld } from "../hooks";
4
+ import { useWorld } from ".";
5
5
 
6
6
  export const useTickerCallback = <T = unknown>({
7
7
  isEnabled = true,
@@ -25,7 +25,7 @@ export const useTilingSprite = ({ texture = "", tilePosition, ...options }: UseT
25
25
  }
26
26
 
27
27
  return new TilingSprite({});
28
- }, [texture, frames]);
28
+ }, [texture, isFetched]);
29
29
 
30
30
  useObject({ object: sprite, ...options });
31
31
 
@@ -33,7 +33,7 @@ export const useTilingSprite = ({ texture = "", tilePosition, ...options }: UseT
33
33
  if (tilePosition && sprite) {
34
34
  sprite.tilePosition = tilePosition;
35
35
  }
36
- }, [tilePosition, sprite?.uid]);
36
+ }, [tilePosition, sprite]);
37
37
 
38
38
  return sprite;
39
39
  };
@@ -29,20 +29,20 @@ export const Layer: React.FC<PropsWithChildren & LayerOptions> = ({ options, chi
29
29
  return () => {
30
30
  removeObjectFromStaeg(container);
31
31
  };
32
- }, [container?.uid]);
32
+ }, [container, addObjectIntoParent, addObjectIntoStage, removeObjectFromParent, removeObjectFromStaeg]);
33
33
 
34
34
  const addObject = useCallback(
35
35
  (thing: Container) => {
36
36
  container.addChild(thing);
37
37
  },
38
- [container?.uid]
38
+ [container]
39
39
  );
40
40
 
41
41
  const removeObject = useCallback(
42
42
  (thing: Container) => {
43
43
  container.removeChild(thing);
44
44
  },
45
- [container?.uid]
45
+ [container]
46
46
  );
47
47
 
48
48
  const conextValue = useMemo<LayerContextValue>(
@@ -50,7 +50,7 @@ export const Layer: React.FC<PropsWithChildren & LayerOptions> = ({ options, chi
50
50
  addObject,
51
51
  removeObject
52
52
  }),
53
- [container?.uid]
53
+ [addObject, removeObject]
54
54
  );
55
55
 
56
56
  return <LayerContext.Provider value={conextValue}>{children}</LayerContext.Provider>;
@@ -1,4 +1,4 @@
1
- import React, { PropsWithChildren, createContext, useEffect, useMemo, useRef, useState } from "react";
1
+ import React, { PropsWithChildren, createContext, useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import Matter, { Engine, Runner, World } from "matter-js";
3
3
  import { MatterPhysicsConfig, MatterPhysics } from "./types";
4
4
 
@@ -47,31 +47,37 @@ export const MatterPhysicsContextProvider: React.FC<PropsWithChildren & MatterPh
47
47
 
48
48
  const runner = useMemo(() => Runner.create(), []);
49
49
 
50
- const addBody = (body: Matter.Body | Matter.Composite) => {
51
- if (engine?.world) {
52
- World.add(engine?.world, body);
53
- }
54
- };
50
+ const addBody = useCallback(
51
+ (body: Matter.Body | Matter.Composite) => {
52
+ if (engine?.world) {
53
+ World.add(engine?.world, body);
54
+ }
55
+ },
56
+ [engine]
57
+ );
55
58
 
56
- const removeBody = (body: Matter.Body | Matter.Composite) => {
57
- if (engine?.world) {
58
- World.remove(engine?.world, body);
59
- }
60
- };
59
+ const removeBody = useCallback(
60
+ (body: Matter.Body | Matter.Composite) => {
61
+ if (engine?.world) {
62
+ World.remove(engine?.world, body);
63
+ }
64
+ },
65
+ [engine]
66
+ );
61
67
 
62
- const run = () => {
68
+ const run = useCallback(() => {
63
69
  if (!isRunningRef.current) {
64
70
  Runner.run(runner, engine);
65
71
  isRunningRef.current = true;
66
72
  }
67
- };
73
+ }, [runner, engine]);
68
74
 
69
- const stop = () => {
75
+ const stop = useCallback(() => {
70
76
  if (isRunningRef.current) {
71
77
  Runner.stop(runner);
72
78
  isRunningRef.current = false;
73
79
  }
74
- };
80
+ }, [runner]);
75
81
 
76
82
  const conextValue = useMemo(
77
83
  () => ({
@@ -83,7 +89,7 @@ export const MatterPhysicsContextProvider: React.FC<PropsWithChildren & MatterPh
83
89
  addBody,
84
90
  removeBody
85
91
  }),
86
- [runner, engine, localConfig]
92
+ [runner, engine, localConfig, addBody, removeBody, run, stop]
87
93
  );
88
94
 
89
95
  useEffect(() => {
@@ -94,7 +100,7 @@ export const MatterPhysicsContextProvider: React.FC<PropsWithChildren & MatterPh
94
100
  return () => {
95
101
  stop();
96
102
  };
97
- }, [isRunning]);
103
+ }, [isRunning, run, stop]);
98
104
 
99
105
  return <MatterPhysicsContext.Provider value={conextValue}>{children}</MatterPhysicsContext.Provider>;
100
106
  };
@@ -22,5 +22,5 @@ export const useCollisionEventHandler = ({ isEnabled = true, event, callback }:
22
22
  return () => {
23
23
  Events.off(engine, event, callback);
24
24
  };
25
- }, [isEnabled, callback, event]);
25
+ }, [isEnabled, callback, event, engine]);
26
26
  };
@@ -26,5 +26,5 @@ export const usePhysicsEngineEventHandler = ({
26
26
  return () => {
27
27
  Events.off(engine, event, callback);
28
28
  };
29
- }, [isEnabled, event, callback]);
29
+ }, [isEnabled, event, callback, engine]);
30
30
  };
@@ -21,5 +21,5 @@ export const usePhysicsTickerCallback = ({ isEnabled = true, callback }: UsePhys
21
21
  return () => {
22
22
  Events.off(runner, "tick", callback);
23
23
  };
24
- }, [isEnabled, callback]);
24
+ }, [isEnabled, callback, runner]);
25
25
  };
@@ -8,19 +8,13 @@ export const Stage: React.FC<PropsWithChildren> = ({ children }) => {
8
8
  const { application } = useWorld();
9
9
  const [things, setThings] = useState<Container[]>([]);
10
10
 
11
- const addObject = useCallback(
12
- (thing: Container) => {
13
- setThings((oldThings) => [...oldThings, thing]);
14
- },
15
- [things]
16
- );
11
+ const addObject = useCallback((thing: Container) => {
12
+ setThings((oldThings) => [...oldThings, thing]);
13
+ }, []);
17
14
 
18
- const removeObject = useCallback(
19
- (thing: Container) => {
20
- setThings((oldThings) => oldThings.filter(({ uid }) => uid === thing.uid));
21
- },
22
- [application]
23
- );
15
+ const removeObject = useCallback((thing: Container) => {
16
+ setThings((oldThings) => oldThings.filter(({ uid }) => uid === thing.uid));
17
+ }, []);
24
18
 
25
19
  const conextValue = useMemo<StageContextValue>(
26
20
  () => ({
@@ -41,7 +41,7 @@ export const World: React.FC<PropsWithChildren & WorldProps> = ({ children, even
41
41
  width: applicationRef.screen.width,
42
42
  height: applicationRef.screen.height
43
43
  };
44
- }, [size, canvasRef.current, applicationRef]);
44
+ }, [size, canvasRef, applicationRef]);
45
45
 
46
46
  useEffect(() => {
47
47
  if (!applicationRef) {
@@ -50,14 +50,14 @@ export const World: React.FC<PropsWithChildren & WorldProps> = ({ children, even
50
50
  setApplicationRef(application);
51
51
  });
52
52
  }
53
- }, []);
53
+ }, [applicationRef]);
54
54
 
55
55
  useEffect(() => {
56
56
  if (applicationRef && canvasRef.current) {
57
57
  applicationRef.resizeTo = canvasRef.current;
58
58
  applicationRef.resize();
59
59
  }
60
- }, [canvasRef.current, applicationRef]);
60
+ }, [canvasRef, applicationRef]);
61
61
 
62
62
  const conextValue = useMemo<WorldContextValue>(
63
63
  () => ({
@@ -71,14 +71,15 @@ export const World: React.FC<PropsWithChildren & WorldProps> = ({ children, even
71
71
  if (!applicationRef) {
72
72
  return;
73
73
  }
74
- canvasRef.current?.appendChild(applicationRef.canvas);
74
+ const canvas = canvasRef.current;
75
+ canvas?.appendChild(applicationRef.canvas);
75
76
 
76
77
  return () => {
77
78
  if (applicationRef) {
78
- canvasRef.current?.removeChild(applicationRef.canvas);
79
+ canvas?.removeChild(applicationRef.canvas);
79
80
  }
80
81
  };
81
- }, [canvasRef.current, applicationRef]);
82
+ }, [canvasRef, applicationRef]);
82
83
 
83
84
  useEffect(() => {
84
85
  if (applicationRef) {