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.
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/src/assets-manager/AssetsManager.context.d.ts +15 -0
- package/dist/src/assets-manager/AssetsManager.context.js +6 -0
- package/dist/src/assets-manager/AssetsManager.d.ts +2 -0
- package/dist/src/assets-manager/AssetsManager.js +43 -0
- package/dist/src/assets-manager/index.d.ts +3 -0
- package/dist/src/assets-manager/index.js +3 -0
- package/dist/src/assets-manager/useAssetManager.d.ts +1 -0
- package/dist/src/assets-manager/useAssetManager.js +5 -0
- package/dist/src/camera/Camera.context.d.ts +13 -0
- package/dist/src/camera/Camera.context.js +2 -0
- package/dist/src/camera/Camera.d.ts +7 -0
- package/dist/src/camera/Camera.js +99 -0
- package/dist/src/camera/index.d.ts +2 -0
- package/dist/src/camera/index.js +2 -0
- package/dist/src/game-context/Game.context.d.ts +39 -0
- package/dist/src/game-context/Game.context.js +85 -0
- package/dist/src/game-context/index.d.ts +1 -0
- package/dist/src/game-context/index.js +1 -0
- package/dist/src/game-objects/GameObjectPhysicalObjectConfig.d.ts +4 -0
- package/dist/src/game-objects/GameObjectPhysicalObjectConfig.js +1 -0
- package/dist/src/game-objects/index.d.ts +2 -0
- package/dist/src/game-objects/index.js +2 -0
- package/dist/src/game-objects/usePhysicalObject.d.ts +7 -0
- package/dist/src/game-objects/usePhysicalObject.js +17 -0
- package/dist/src/game-objects/usePhysicalObjectFromConfig.d.ts +6 -0
- package/dist/src/game-objects/usePhysicalObjectFromConfig.js +13 -0
- package/dist/src/game-objects/useWalls.d.ts +11 -0
- package/dist/src/game-objects/useWalls.js +59 -0
- package/dist/src/hooks/index.d.ts +12 -0
- package/dist/src/hooks/index.js +12 -0
- package/dist/src/hooks/useAnimatedSprite.d.ts +10 -0
- package/dist/src/hooks/useAnimatedSprite.js +41 -0
- package/dist/src/hooks/useCamera.d.ts +1 -0
- package/dist/src/hooks/useCamera.js +5 -0
- package/dist/src/hooks/useCollisionDetection.d.ts +9 -0
- package/dist/src/hooks/useCollisionDetection.js +21 -0
- package/dist/src/hooks/useGame.d.ts +1 -0
- package/dist/src/hooks/useGame.js +5 -0
- package/dist/src/hooks/useGlobalEventHandler.d.ts +5 -0
- package/dist/src/hooks/useGlobalEventHandler.js +15 -0
- package/dist/src/hooks/useLayerContext.d.ts +1 -0
- package/dist/src/hooks/useLayerContext.js +5 -0
- package/dist/src/hooks/useObject.d.ts +6 -0
- package/dist/src/hooks/useObject.js +54 -0
- package/dist/src/hooks/useSprite.d.ts +6 -0
- package/dist/src/hooks/useSprite.js +21 -0
- package/dist/src/hooks/useStage.d.ts +1 -0
- package/dist/src/hooks/useStage.js +5 -0
- package/dist/src/hooks/useTexture.d.ts +9 -0
- package/dist/src/hooks/useTexture.js +14 -0
- package/dist/src/hooks/useTickerCallback.d.ts +5 -0
- package/dist/src/hooks/useTickerCallback.js +14 -0
- package/dist/src/hooks/useTilingSprite.d.ts +6 -0
- package/dist/src/hooks/useTilingSprite.js +26 -0
- package/dist/src/hooks/useWorld.d.ts +1 -0
- package/dist/src/hooks/useWorld.js +5 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +10 -0
- package/dist/src/layer/Layer.context.d.ts +6 -0
- package/dist/src/layer/Layer.context.js +2 -0
- package/dist/src/layer/Layer.d.ts +6 -0
- package/dist/src/layer/Layer.js +34 -0
- package/dist/src/layer/index.d.ts +1 -0
- package/dist/src/layer/index.js +1 -0
- package/dist/src/physics/MatterPhysics.context.d.ts +8 -0
- package/dist/src/physics/MatterPhysics.context.js +74 -0
- package/dist/src/physics/index.d.ts +4 -0
- package/dist/src/physics/index.js +4 -0
- package/dist/src/physics/types.d.ts +24 -0
- package/dist/src/physics/types.js +1 -0
- package/dist/src/physics/useCollisionEventHandler.d.ts +7 -0
- package/dist/src/physics/useCollisionEventHandler.js +15 -0
- package/dist/src/physics/usePhysicsEngineEventHandler.d.ts +8 -0
- package/dist/src/physics/usePhysicsEngineEventHandler.js +15 -0
- package/dist/src/physics/usePhysicsTickerCallback.d.ts +7 -0
- package/dist/src/physics/usePhysicsTickerCallback.js +15 -0
- package/dist/src/stage/Stage.context.d.ts +6 -0
- package/dist/src/stage/Stage.context.js +2 -0
- package/dist/src/stage/Stage.d.ts +2 -0
- package/dist/src/stage/Stage.js +29 -0
- package/dist/src/stage/index.d.ts +1 -0
- package/dist/src/stage/index.js +1 -0
- package/dist/src/types.d.ts +7 -0
- package/dist/src/types.js +7 -0
- package/dist/src/world/World.context.d.ts +10 -0
- package/dist/src/world/World.context.js +2 -0
- package/dist/src/world/World.d.ts +11 -0
- package/dist/src/world/World.js +66 -0
- package/dist/src/world/index.d.ts +2 -0
- package/dist/src/world/index.js +2 -0
- package/package.json +52 -0
- package/src/assets-manager/AssetsManager.context.tsx +23 -0
- package/src/assets-manager/AssetsManager.tsx +54 -0
- package/src/assets-manager/index.ts +3 -0
- package/src/assets-manager/useAssetManager.ts +7 -0
- package/src/camera/Camera.context.tsx +17 -0
- package/src/camera/Camera.tsx +153 -0
- package/src/camera/index.ts +2 -0
- package/src/game-context/Game.context.tsx +133 -0
- package/src/game-context/index.ts +1 -0
- package/src/game-objects/GameObjectPhysicalObjectConfig.ts +18 -0
- package/src/game-objects/index.ts +2 -0
- package/src/game-objects/usePhysicalObject.ts +24 -0
- package/src/game-objects/usePhysicalObjectFromConfig.ts +22 -0
- package/src/game-objects/useWalls.ts +93 -0
- package/src/hooks/index.ts +12 -0
- package/src/hooks/useAnimatedSprite.ts +65 -0
- package/src/hooks/useCamera.ts +6 -0
- package/src/hooks/useCollisionDetection.ts +35 -0
- package/src/hooks/useGame.ts +6 -0
- package/src/hooks/useGlobalEventHandler.ts +27 -0
- package/src/hooks/useLayerContext.ts +6 -0
- package/src/hooks/useObject.ts +81 -0
- package/src/hooks/useSprite.ts +33 -0
- package/src/hooks/useStage.ts +7 -0
- package/src/hooks/useTexture.ts +22 -0
- package/src/hooks/useTickerCallback.ts +25 -0
- package/src/hooks/useTilingSprite.ts +39 -0
- package/src/hooks/useWorld.ts +7 -0
- package/src/index.ts +10 -0
- package/src/layer/Layer.context.tsx +9 -0
- package/src/layer/Layer.tsx +57 -0
- package/src/layer/index.ts +1 -0
- package/src/physics/MatterPhysics.context.tsx +100 -0
- package/src/physics/index.ts +4 -0
- package/src/physics/types.ts +27 -0
- package/src/physics/useCollisionEventHandler.ts +26 -0
- package/src/physics/usePhysicsEngineEventHandler.ts +30 -0
- package/src/physics/usePhysicsTickerCallback.ts +25 -0
- package/src/stage/Stage.context.tsx +9 -0
- package/src/stage/Stage.tsx +50 -0
- package/src/stage/index.ts +1 -0
- package/src/types.ts +8 -0
- package/src/world/World.context.tsx +13 -0
- package/src/world/World.tsx +93 -0
- 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 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,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
|
+
};
|
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,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,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,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
|
+
};
|