pixi-fusion 1.0.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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/dist/src/assets-manager/AssetsManager.context.d.ts +15 -0
  4. package/dist/src/assets-manager/AssetsManager.context.js +6 -0
  5. package/dist/src/assets-manager/AssetsManager.d.ts +2 -0
  6. package/dist/src/assets-manager/AssetsManager.js +43 -0
  7. package/dist/src/assets-manager/index.d.ts +3 -0
  8. package/dist/src/assets-manager/index.js +3 -0
  9. package/dist/src/assets-manager/useAssetManager.d.ts +1 -0
  10. package/dist/src/assets-manager/useAssetManager.js +5 -0
  11. package/dist/src/camera/Camera.context.d.ts +13 -0
  12. package/dist/src/camera/Camera.context.js +2 -0
  13. package/dist/src/camera/Camera.d.ts +7 -0
  14. package/dist/src/camera/Camera.js +99 -0
  15. package/dist/src/camera/index.d.ts +2 -0
  16. package/dist/src/camera/index.js +2 -0
  17. package/dist/src/game-context/Game.context.d.ts +39 -0
  18. package/dist/src/game-context/Game.context.js +85 -0
  19. package/dist/src/game-context/index.d.ts +1 -0
  20. package/dist/src/game-context/index.js +1 -0
  21. package/dist/src/game-objects/GameObjectPhysicalObjectConfig.d.ts +4 -0
  22. package/dist/src/game-objects/GameObjectPhysicalObjectConfig.js +1 -0
  23. package/dist/src/game-objects/index.d.ts +2 -0
  24. package/dist/src/game-objects/index.js +2 -0
  25. package/dist/src/game-objects/usePhysicalObject.d.ts +7 -0
  26. package/dist/src/game-objects/usePhysicalObject.js +17 -0
  27. package/dist/src/game-objects/usePhysicalObjectFromConfig.d.ts +6 -0
  28. package/dist/src/game-objects/usePhysicalObjectFromConfig.js +13 -0
  29. package/dist/src/game-objects/useWalls.d.ts +11 -0
  30. package/dist/src/game-objects/useWalls.js +59 -0
  31. package/dist/src/hooks/index.d.ts +12 -0
  32. package/dist/src/hooks/index.js +12 -0
  33. package/dist/src/hooks/useAnimatedSprite.d.ts +10 -0
  34. package/dist/src/hooks/useAnimatedSprite.js +41 -0
  35. package/dist/src/hooks/useCamera.d.ts +1 -0
  36. package/dist/src/hooks/useCamera.js +5 -0
  37. package/dist/src/hooks/useCollisionDetection.d.ts +9 -0
  38. package/dist/src/hooks/useCollisionDetection.js +21 -0
  39. package/dist/src/hooks/useGame.d.ts +1 -0
  40. package/dist/src/hooks/useGame.js +5 -0
  41. package/dist/src/hooks/useGlobalEventHandler.d.ts +5 -0
  42. package/dist/src/hooks/useGlobalEventHandler.js +15 -0
  43. package/dist/src/hooks/useLayerContext.d.ts +1 -0
  44. package/dist/src/hooks/useLayerContext.js +5 -0
  45. package/dist/src/hooks/useObject.d.ts +6 -0
  46. package/dist/src/hooks/useObject.js +54 -0
  47. package/dist/src/hooks/useSprite.d.ts +6 -0
  48. package/dist/src/hooks/useSprite.js +21 -0
  49. package/dist/src/hooks/useStage.d.ts +1 -0
  50. package/dist/src/hooks/useStage.js +5 -0
  51. package/dist/src/hooks/useTexture.d.ts +9 -0
  52. package/dist/src/hooks/useTexture.js +14 -0
  53. package/dist/src/hooks/useTickerCallback.d.ts +5 -0
  54. package/dist/src/hooks/useTickerCallback.js +14 -0
  55. package/dist/src/hooks/useTilingSprite.d.ts +6 -0
  56. package/dist/src/hooks/useTilingSprite.js +26 -0
  57. package/dist/src/hooks/useWorld.d.ts +1 -0
  58. package/dist/src/hooks/useWorld.js +5 -0
  59. package/dist/src/index.d.ts +10 -0
  60. package/dist/src/index.js +10 -0
  61. package/dist/src/layer/Layer.context.d.ts +6 -0
  62. package/dist/src/layer/Layer.context.js +2 -0
  63. package/dist/src/layer/Layer.d.ts +6 -0
  64. package/dist/src/layer/Layer.js +34 -0
  65. package/dist/src/layer/index.d.ts +1 -0
  66. package/dist/src/layer/index.js +1 -0
  67. package/dist/src/physics/MatterPhysics.context.d.ts +8 -0
  68. package/dist/src/physics/MatterPhysics.context.js +74 -0
  69. package/dist/src/physics/index.d.ts +4 -0
  70. package/dist/src/physics/index.js +4 -0
  71. package/dist/src/physics/types.d.ts +24 -0
  72. package/dist/src/physics/types.js +1 -0
  73. package/dist/src/physics/useCollisionEventHandler.d.ts +7 -0
  74. package/dist/src/physics/useCollisionEventHandler.js +15 -0
  75. package/dist/src/physics/usePhysicsEngineEventHandler.d.ts +8 -0
  76. package/dist/src/physics/usePhysicsEngineEventHandler.js +15 -0
  77. package/dist/src/physics/usePhysicsTickerCallback.d.ts +7 -0
  78. package/dist/src/physics/usePhysicsTickerCallback.js +15 -0
  79. package/dist/src/stage/Stage.context.d.ts +6 -0
  80. package/dist/src/stage/Stage.context.js +2 -0
  81. package/dist/src/stage/Stage.d.ts +2 -0
  82. package/dist/src/stage/Stage.js +29 -0
  83. package/dist/src/stage/index.d.ts +1 -0
  84. package/dist/src/stage/index.js +1 -0
  85. package/dist/src/types.d.ts +7 -0
  86. package/dist/src/types.js +7 -0
  87. package/dist/src/world/World.context.d.ts +10 -0
  88. package/dist/src/world/World.context.js +2 -0
  89. package/dist/src/world/World.d.ts +11 -0
  90. package/dist/src/world/World.js +66 -0
  91. package/dist/src/world/index.d.ts +2 -0
  92. package/dist/src/world/index.js +2 -0
  93. package/package.json +52 -0
  94. package/src/assets-manager/AssetsManager.context.tsx +23 -0
  95. package/src/assets-manager/AssetsManager.tsx +54 -0
  96. package/src/assets-manager/index.ts +3 -0
  97. package/src/assets-manager/useAssetManager.ts +7 -0
  98. package/src/camera/Camera.context.tsx +17 -0
  99. package/src/camera/Camera.tsx +153 -0
  100. package/src/camera/index.ts +2 -0
  101. package/src/game-context/Game.context.tsx +133 -0
  102. package/src/game-context/index.ts +1 -0
  103. package/src/game-objects/GameObjectPhysicalObjectConfig.ts +18 -0
  104. package/src/game-objects/index.ts +2 -0
  105. package/src/game-objects/usePhysicalObject.ts +24 -0
  106. package/src/game-objects/usePhysicalObjectFromConfig.ts +22 -0
  107. package/src/game-objects/useWalls.ts +93 -0
  108. package/src/hooks/index.ts +12 -0
  109. package/src/hooks/useAnimatedSprite.ts +65 -0
  110. package/src/hooks/useCamera.ts +6 -0
  111. package/src/hooks/useCollisionDetection.ts +35 -0
  112. package/src/hooks/useGame.ts +6 -0
  113. package/src/hooks/useGlobalEventHandler.ts +27 -0
  114. package/src/hooks/useLayerContext.ts +6 -0
  115. package/src/hooks/useObject.ts +81 -0
  116. package/src/hooks/useSprite.ts +33 -0
  117. package/src/hooks/useStage.ts +7 -0
  118. package/src/hooks/useTexture.ts +22 -0
  119. package/src/hooks/useTickerCallback.ts +25 -0
  120. package/src/hooks/useTilingSprite.ts +39 -0
  121. package/src/hooks/useWorld.ts +7 -0
  122. package/src/index.ts +10 -0
  123. package/src/layer/Layer.context.tsx +9 -0
  124. package/src/layer/Layer.tsx +57 -0
  125. package/src/layer/index.ts +1 -0
  126. package/src/physics/MatterPhysics.context.tsx +100 -0
  127. package/src/physics/index.ts +4 -0
  128. package/src/physics/types.ts +27 -0
  129. package/src/physics/useCollisionEventHandler.ts +26 -0
  130. package/src/physics/usePhysicsEngineEventHandler.ts +30 -0
  131. package/src/physics/usePhysicsTickerCallback.ts +25 -0
  132. package/src/stage/Stage.context.tsx +9 -0
  133. package/src/stage/Stage.tsx +50 -0
  134. package/src/stage/index.ts +1 -0
  135. package/src/types.ts +8 -0
  136. package/src/world/World.context.tsx +13 -0
  137. package/src/world/World.tsx +93 -0
  138. package/src/world/index.ts +2 -0
@@ -0,0 +1,29 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { StageContext } from "./Stage.context";
3
+ import { useWorld } from "../hooks";
4
+ import { Layer } from "../layer";
5
+ export const Stage = ({ children }) => {
6
+ const { application, isInitialized } = useWorld();
7
+ const [things, setThings] = useState([]);
8
+ const addObject = useCallback((thing) => {
9
+ setThings((oldThings) => [...oldThings, thing]);
10
+ }, [things]);
11
+ const removeObject = useCallback((thing) => {
12
+ setThings((oldThings) => oldThings.filter(({ uid }) => uid === thing.uid));
13
+ }, [application]);
14
+ const conextValue = useMemo(() => ({
15
+ addObject,
16
+ removeObject
17
+ }), [addObject, removeObject]);
18
+ useEffect(() => {
19
+ if (!isInitialized) {
20
+ return;
21
+ }
22
+ application.stage.addChild(...things);
23
+ return () => {
24
+ application.stage.removeChild(...things);
25
+ };
26
+ }, [things, isInitialized]);
27
+ return (React.createElement(StageContext.Provider, { value: conextValue },
28
+ React.createElement(Layer, null, children)));
29
+ };
@@ -0,0 +1 @@
1
+ export * from "./Stage";
@@ -0,0 +1 @@
1
+ export * from "./Stage";
@@ -0,0 +1,7 @@
1
+ export declare enum GameStatus {
2
+ READY = 0,
3
+ IN_PROGRESS = 1,
4
+ TIMEDOUT = 2,
5
+ COMPLETED = 3
6
+ }
7
+ export type Nullable<T> = T | null;
@@ -0,0 +1,7 @@
1
+ export var GameStatus;
2
+ (function (GameStatus) {
3
+ GameStatus[GameStatus["READY"] = 0] = "READY";
4
+ GameStatus[GameStatus["IN_PROGRESS"] = 1] = "IN_PROGRESS";
5
+ GameStatus[GameStatus["TIMEDOUT"] = 2] = "TIMEDOUT";
6
+ GameStatus[GameStatus["COMPLETED"] = 3] = "COMPLETED";
7
+ })(GameStatus || (GameStatus = {}));
@@ -0,0 +1,10 @@
1
+ import { Application } from "pixi.js";
2
+ export type WorldContextValue = {
3
+ readonly size: {
4
+ width: number;
5
+ height: number;
6
+ };
7
+ readonly application: Application;
8
+ readonly isInitialized: boolean;
9
+ };
10
+ export declare const WorldContext: import("react").Context<WorldContextValue>;
@@ -0,0 +1,2 @@
1
+ import { createContext } from "react";
2
+ export const WorldContext = createContext({});
@@ -0,0 +1,11 @@
1
+ import React, { PropsWithChildren } from "react";
2
+ import { EventMode } from "pixi.js";
3
+ type WorldProps = {
4
+ eventMode?: EventMode;
5
+ size?: {
6
+ width: number;
7
+ height: number;
8
+ };
9
+ };
10
+ export declare const World: React.FC<PropsWithChildren & WorldProps>;
11
+ export {};
@@ -0,0 +1,66 @@
1
+ import React, { createRef, useEffect, useMemo, useRef, useState } from "react";
2
+ import { Application } from "pixi.js";
3
+ import { WorldContext } from "./World.context";
4
+ import { Stage } from "../stage";
5
+ import { AssetsManager } from "../assets-manager";
6
+ export const World = ({ children, eventMode, size }) => {
7
+ const [isInitialized, setIsInitialized] = useState(false);
8
+ const canvasRef = createRef();
9
+ const application = useRef(new Application());
10
+ const worldSize = useMemo(() => {
11
+ if (size) {
12
+ return { ...size };
13
+ }
14
+ if (canvasRef?.current) {
15
+ const { width, height } = canvasRef.current.getBoundingClientRect();
16
+ return {
17
+ width,
18
+ height
19
+ };
20
+ }
21
+ if (!isInitialized) {
22
+ return {
23
+ width: 0,
24
+ height: 0
25
+ };
26
+ }
27
+ return {
28
+ width: application.current.screen.width,
29
+ height: application.current.screen.height
30
+ };
31
+ }, [size, canvasRef, isInitialized]);
32
+ useEffect(() => {
33
+ if (canvasRef.current && !isInitialized) {
34
+ application.current
35
+ .init({
36
+ resizeTo: canvasRef.current
37
+ })
38
+ .then(() => {
39
+ setIsInitialized(true);
40
+ });
41
+ }
42
+ }, [canvasRef.current, isInitialized]);
43
+ const conextValue = useMemo(() => ({
44
+ application: application.current,
45
+ size: worldSize,
46
+ isInitialized
47
+ }), [isInitialized, worldSize]);
48
+ useEffect(() => {
49
+ if (!isInitialized) {
50
+ return;
51
+ }
52
+ canvasRef.current?.appendChild(application.current.canvas);
53
+ return () => {
54
+ canvasRef.current?.removeChild(application.current.canvas);
55
+ };
56
+ }, [canvasRef.current, isInitialized]);
57
+ useEffect(() => {
58
+ if (application.current) {
59
+ application.current.stage.eventMode = eventMode;
60
+ }
61
+ }, [eventMode, application.current, isInitialized]);
62
+ return (React.createElement(WorldContext.Provider, { value: conextValue },
63
+ React.createElement(AssetsManager, null,
64
+ React.createElement(Stage, null, children),
65
+ React.createElement("div", { style: { width: "100%", height: "100%" }, ref: canvasRef }))));
66
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./World.context";
2
+ export * from "./World";
@@ -0,0 +1,2 @@
1
+ export * from "./World.context";
2
+ export * from "./World";
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "author": "Victor Pishuk <victor.pishuk@gmail.com>",
3
+ "type": "module",
4
+ "bugs": {
5
+ "url": "https://github.com/laverve/fusion/issues"
6
+ },
7
+ "dependencies": {
8
+ "@types/matter-js": "^0.19.7",
9
+ "matter-js": "^0.20.0",
10
+ "pixi.js": "^8.4.1",
11
+ "pixi-viewport": "^5.0.3"
12
+ },
13
+ "peerDependencies": {
14
+ "react": "^18.2.0"
15
+ },
16
+ "description": "This module offers a set of common components needed for playing games.",
17
+ "keywords": [
18
+ "game",
19
+ "engine",
20
+ "pixi.js",
21
+ "matter-js",
22
+ "react"
23
+ ],
24
+ "license": "MIT",
25
+ "main": "./dist/src/index.js",
26
+ "types": "./dist/src/index.d.ts",
27
+ "name": "pixi-fusion",
28
+ "files": [
29
+ "./dist/src/",
30
+ "./src/",
31
+ "package.json",
32
+ "package-lock.json",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/laverve/fusion.git"
42
+ },
43
+ "scripts": {
44
+ "lint": "eslint .",
45
+ "lint:staged": "lint-staged",
46
+ "test": "jest --passWithNoTests",
47
+ "build": "tsc",
48
+ "build:dev": "tsc -w"
49
+ },
50
+ "version": "1.0.0",
51
+ "webpack": "./src/index.ts"
52
+ }
@@ -0,0 +1,23 @@
1
+ import { Texture } from "pixi.js";
2
+ import { createContext } from "react";
3
+
4
+ export type Asset = {
5
+ src: string;
6
+ alias: string;
7
+ };
8
+
9
+ export type AssetsManagerContextValue = {
10
+ isFetching: boolean;
11
+ isFetched: boolean;
12
+ isError: boolean;
13
+ error?: unknown;
14
+ load: (groupId: string, asset: Asset[]) => Promise<void>;
15
+ unload: (groupIdOrAlias: string) => void;
16
+ getAsset: (alias: string) => Texture | undefined;
17
+ };
18
+
19
+ export const AssetsManagerContext = createContext<AssetsManagerContextValue>({
20
+ isFetching: false,
21
+ isFetched: false,
22
+ isError: false
23
+ } as unknown as AssetsManagerContextValue);
@@ -0,0 +1,54 @@
1
+ import React, { PropsWithChildren, useCallback, useEffect, useMemo, useState } from "react";
2
+ import { Asset, AssetsManagerContext, AssetsManagerContextValue } from "./AssetsManager.context";
3
+ import { Assets } from "pixi.js";
4
+
5
+ export const AssetsManager: React.FC<PropsWithChildren> = ({ children }) => {
6
+ const [isFetching, setIsFetching] = useState(false);
7
+ const [isFetched, setIsFetched] = useState(false);
8
+ const [isError, setIsError] = useState(false);
9
+ const [error, setError] = useState<unknown>();
10
+
11
+ useEffect(() => {
12
+ Assets.cache.reset();
13
+ }, []);
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
+ );
31
+
32
+ const unload = useCallback((groupId: string) => {
33
+ Assets.unloadBundle(groupId);
34
+ }, []);
35
+
36
+ const getAsset = useCallback((alias: string) => {
37
+ return Assets.get(alias);
38
+ }, []);
39
+
40
+ const conextValue = useMemo<AssetsManagerContextValue>(
41
+ () => ({
42
+ load,
43
+ unload,
44
+ getAsset,
45
+ isFetching,
46
+ isFetched,
47
+ isError,
48
+ error
49
+ }),
50
+ [load, unload, getAsset, isFetching, isFetched, isError, error]
51
+ );
52
+
53
+ return <AssetsManagerContext.Provider value={conextValue}>{children}</AssetsManagerContext.Provider>;
54
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./AssetsManager";
2
+ export * from "./AssetsManager.context";
3
+ export * from "./useAssetManager";
@@ -0,0 +1,7 @@
1
+ import { useContext } from "react";
2
+
3
+ import { AssetsManagerContext } from "./AssetsManager.context";
4
+
5
+ export const useAssetManager = () => {
6
+ return useContext(AssetsManagerContext);
7
+ };
@@ -0,0 +1,17 @@
1
+ import { Container } from "pixi.js";
2
+ import { createContext } from "react";
3
+
4
+ export type EnsureVisibleOptions = {
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ resizeToFit?: boolean;
10
+ };
11
+
12
+ export type CameraContextValue = {
13
+ follow: (object: Container) => void;
14
+ ensureVisible: (options: EnsureVisibleOptions) => void;
15
+ };
16
+
17
+ export const CameraContext = createContext<CameraContextValue>({} as unknown as CameraContextValue);
@@ -0,0 +1,153 @@
1
+ import React, { PropsWithChildren, useCallback, useEffect, useMemo, useState } from "react";
2
+ import { Container } from "pixi.js";
3
+ import { Viewport as ViewportContainer } from "pixi-viewport";
4
+ import { StageContextValue } from "../stage/Stage.context";
5
+ import { Layer } from "../layer";
6
+ import { useStage, useWorld } from "../hooks";
7
+ import { LayerContext } from "../layer/Layer.context";
8
+ import { CameraContext, EnsureVisibleOptions } from "./Camera.context";
9
+
10
+ type CameraProps = {
11
+ clampZoom?: Parameters<ViewportContainer["clampZoom"]>[0];
12
+ };
13
+
14
+ export const Camera: React.FC<CameraProps & PropsWithChildren> = ({ children, clampZoom }) => {
15
+ const [isReady, setIsReady] = useState(false);
16
+ const [followContainer, setFollowContainer] = useState<Container | null>(null);
17
+ const [ensureVisibleOptions, setEnsureVisibleOptions] = useState<EnsureVisibleOptions | null>(null);
18
+ const { application, isInitialized, size } = useWorld();
19
+ const { addObject: addObjectToStage, removeObject: removeObjectFromStage } = useStage();
20
+ const [things, setThings] = useState<Container[]>([]);
21
+ const viewport = useMemo(
22
+ () =>
23
+ isInitialized
24
+ ? new ViewportContainer({
25
+ worldHeight: size.height,
26
+ worldWidth: size.width,
27
+ screenHeight: application.canvas.height,
28
+ screenWidth: application.canvas.width,
29
+ events: application?.renderer?.events
30
+ })
31
+ : null,
32
+ [size, application, isInitialized]
33
+ );
34
+
35
+ const addObject = useCallback(
36
+ (thing: Container) => {
37
+ setThings((oldThings) => [...oldThings, thing]);
38
+ },
39
+ [things]
40
+ );
41
+
42
+ const removeObject = useCallback(
43
+ (thing: Container) => {
44
+ setThings((oldThings) => oldThings.filter(({ uid }) => uid === thing.uid));
45
+ },
46
+ [application]
47
+ );
48
+
49
+ const conextValue = useMemo<StageContextValue>(
50
+ () => ({
51
+ addObject,
52
+ removeObject
53
+ }),
54
+ [addObject, removeObject]
55
+ );
56
+
57
+ useEffect(() => {
58
+ if (viewport && clampZoom) {
59
+ viewport.clampZoom(clampZoom);
60
+ }
61
+ }, [clampZoom, isInitialized]);
62
+
63
+ useEffect(() => {
64
+ if (viewport) {
65
+ if (size.width) {
66
+ viewport.width = size.width;
67
+ }
68
+ if (size.height) {
69
+ viewport.height = size.height;
70
+ }
71
+ }
72
+ }, [size, viewport?.uid, isInitialized]);
73
+
74
+ useEffect(() => {
75
+ if (!isInitialized || !viewport) {
76
+ return;
77
+ }
78
+ viewport?.moveCenter(0, 0);
79
+ }, [viewport, size, isInitialized]);
80
+
81
+ useEffect(() => {
82
+ if (!viewport) {
83
+ return;
84
+ }
85
+
86
+ addObjectToStage(viewport);
87
+ setIsReady(true);
88
+
89
+ return () => {
90
+ removeObjectFromStage(viewport);
91
+ setIsReady(false);
92
+ };
93
+ }, [application, isInitialized]);
94
+
95
+ useEffect(() => {
96
+ if (!viewport || !isReady) {
97
+ return;
98
+ }
99
+
100
+ viewport.addChild(...things);
101
+
102
+ return () => {
103
+ viewport.removeChild(...things);
104
+ };
105
+ }, [things, viewport, isReady]);
106
+
107
+ useEffect(() => {
108
+ if (isReady && viewport && followContainer) {
109
+ viewport.follow(followContainer, { radius: 300 });
110
+ }
111
+ }, [isReady, viewport, followContainer]);
112
+
113
+ useEffect(() => {
114
+ if (isReady && viewport && ensureVisibleOptions) {
115
+ viewport.ensureVisible(
116
+ ensureVisibleOptions.x,
117
+ ensureVisibleOptions.y,
118
+ ensureVisibleOptions.width,
119
+ ensureVisibleOptions.height,
120
+ ensureVisibleOptions.resizeToFit
121
+ );
122
+ }
123
+ }, [isReady, viewport, ensureVisibleOptions]);
124
+
125
+ const followCallback = useCallback(
126
+ (container: Container) => {
127
+ setFollowContainer(container);
128
+ },
129
+ [followContainer]
130
+ );
131
+
132
+ const ensureVisibleCallback = useCallback(
133
+ (options: EnsureVisibleOptions) => {
134
+ setEnsureVisibleOptions(options);
135
+ },
136
+ [followContainer]
137
+ );
138
+
139
+ const value = useMemo(() => {
140
+ return {
141
+ follow: followCallback,
142
+ ensureVisible: ensureVisibleCallback
143
+ };
144
+ }, [followCallback]);
145
+
146
+ return (
147
+ <CameraContext.Provider value={value}>
148
+ <LayerContext.Provider value={conextValue}>
149
+ <Layer>{children}</Layer>
150
+ </LayerContext.Provider>
151
+ </CameraContext.Provider>
152
+ );
153
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./Camera";
2
+ export * from "./Camera.context";
@@ -0,0 +1,133 @@
1
+ import React, { PropsWithChildren, createContext, useMemo, useRef, useState } from "react";
2
+ import { GameStatus } from "../types";
3
+
4
+ export type GameContextValue = {
5
+ readonly reset: () => void;
6
+ readonly start: () => void;
7
+ readonly stop: () => void;
8
+ readonly timeout: number;
9
+ readonly status: GameStatus;
10
+ readonly startTime: number | null;
11
+ readonly endTime: number | null;
12
+ };
13
+
14
+ export type GameContextProviderEvents = {
15
+ onStart?: (event: { startTime: number | null; status: GameStatus; endTime: number | null }) => unknown;
16
+ onStop?: (event: { startTime: number | null; status: GameStatus; endTime: number | null }) => unknown;
17
+ onTimedOut?: (event: { startTime: number | null; status: GameStatus; endTime: number | null }) => unknown;
18
+ onReset?: (event: { startTime: number | null; status: GameStatus; endTime: number | null }) => unknown;
19
+ };
20
+
21
+ export const GameContext = createContext<GameContextValue>({
22
+ reset: () => {},
23
+ start: () => {},
24
+ stop: () => {},
25
+ timeout: 0,
26
+ status: GameStatus.READY,
27
+ startTime: null,
28
+ endTime: null
29
+ });
30
+
31
+ export type GameContextProviderProps = PropsWithChildren & {
32
+ timeout?: number;
33
+ events?: GameContextProviderEvents;
34
+ };
35
+
36
+ export const GameContextProvider: React.FC<GameContextProviderProps> = ({
37
+ children,
38
+ timeout: inputTimeout = 0,
39
+ events
40
+ }) => {
41
+ const timerRef = useRef<NodeJS.Timeout>();
42
+ const timeout = Math.max(0, inputTimeout);
43
+
44
+ const [status, setStatus] = useState(GameStatus.READY);
45
+ const [startTime, setStartTime] = useState<number | null>(null);
46
+ const [endTime, setEndTime] = useState<number | null>(null);
47
+
48
+ const onTimedOut = () => {
49
+ const newEndTime = +new Date();
50
+ const newStatus = GameStatus.TIMEDOUT;
51
+
52
+ setEndTime(newEndTime);
53
+ setStatus(GameStatus.TIMEDOUT);
54
+
55
+ events?.onTimedOut?.({
56
+ startTime,
57
+ endTime: newEndTime,
58
+ status: newStatus
59
+ });
60
+ };
61
+
62
+ const start = () => {
63
+ const newStartTime = +new Date();
64
+ const newStatus = GameStatus.IN_PROGRESS;
65
+ const newEndTime = null;
66
+
67
+ setStatus(newStatus);
68
+ setStartTime(newStartTime);
69
+ setEndTime(newEndTime);
70
+
71
+ if (timeout !== 0) {
72
+ timerRef.current = setTimeout(() => {
73
+ onTimedOut();
74
+ }, timeout * 1000);
75
+ }
76
+
77
+ events?.onStart?.({
78
+ startTime: newStartTime,
79
+ endTime: newEndTime,
80
+ status: newStatus
81
+ });
82
+ };
83
+
84
+ const reset = () => {
85
+ const newStartTime = null;
86
+ const newStatus = GameStatus.READY;
87
+ const newEndTime = null;
88
+
89
+ setStatus(newStatus);
90
+ setStartTime(newStartTime);
91
+ setEndTime(newEndTime);
92
+
93
+ if (timerRef.current) {
94
+ clearTimeout(timerRef.current);
95
+ }
96
+
97
+ events?.onReset?.({
98
+ startTime: newStartTime,
99
+ endTime: newEndTime,
100
+ status: newStatus
101
+ });
102
+ };
103
+
104
+ const stop = () => {
105
+ const newStatus = GameStatus.COMPLETED;
106
+ const newEndTime = +new Date();
107
+
108
+ clearTimeout(timerRef.current);
109
+ setEndTime(+new Date());
110
+ setStatus(GameStatus.COMPLETED);
111
+
112
+ events?.onStop?.({
113
+ startTime,
114
+ endTime: newEndTime,
115
+ status: newStatus
116
+ });
117
+ };
118
+
119
+ const contextValue = useMemo(
120
+ () => ({
121
+ status,
122
+ startTime,
123
+ endTime,
124
+ timeout,
125
+ stop,
126
+ start,
127
+ reset
128
+ }),
129
+ [timeout, status, startTime, endTime]
130
+ );
131
+
132
+ return <GameContext.Provider value={contextValue}>{children}</GameContext.Provider>;
133
+ };
@@ -0,0 +1 @@
1
+ export * from "./Game.context";
@@ -0,0 +1,18 @@
1
+ import { IBodyDefinition } from "matter-js";
2
+
3
+ type PhysicalObjectConfig = Pick<
4
+ IBodyDefinition,
5
+ | "vertices"
6
+ | "label"
7
+ | "inertia"
8
+ | "position"
9
+ | "friction"
10
+ | "frictionAir"
11
+ | "frictionStatic"
12
+ | "parts"
13
+ | "isStatic"
14
+ | "isSensor"
15
+ | "collisionFilter"
16
+ >;
17
+
18
+ export type GameObjectPhysicalObjectConfig = PhysicalObjectConfig;
@@ -0,0 +1,2 @@
1
+ export * from "./usePhysicalObject";
2
+ export * from "./useWalls";
@@ -0,0 +1,24 @@
1
+ import { Body, Composite } from "matter-js";
2
+ import { useContext, useEffect } from "react";
3
+ import { MatterPhysicsContext } from "../physics";
4
+ import { Nullable } from "../types";
5
+
6
+ export const usePhysicalObject = ({ physicalObject }: { physicalObject: Nullable<Body | Composite> }) => {
7
+ const { addBody, removeBody } = useContext(MatterPhysicsContext);
8
+
9
+ useEffect(() => {
10
+ if (!physicalObject) {
11
+ return () => {};
12
+ }
13
+
14
+ addBody(physicalObject);
15
+
16
+ return () => {
17
+ removeBody(physicalObject);
18
+ };
19
+ }, [physicalObject?.id]);
20
+
21
+ return {
22
+ physicalObject
23
+ };
24
+ };