diff --git a/package.json b/package.json index 3f65c95..ff66fff 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.6.0", + "sass": "^1.100.0", "typescript": "~6.0.2", "typescript-eslint": "^8.59.2", "vite": "^8.0.12" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b34c723..60ab854 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,7 +56,7 @@ importers: version: 19.2.3(@types/react@19.2.15) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.2(vite@8.0.14(@types/node@24.12.4)) + version: 6.0.2(vite@8.0.14(@types/node@24.12.4)(sass@1.100.0)) eslint: specifier: ^10.3.0 version: 10.4.1 @@ -69,6 +69,9 @@ importers: globals: specifier: ^17.6.0 version: 17.6.0 + sass: + specifier: ^1.100.0 + version: 1.100.0 typescript: specifier: ~6.0.2 version: 6.0.3 @@ -77,7 +80,7 @@ importers: version: 8.60.0(eslint@10.4.1)(typescript@6.0.3) vite: specifier: ^8.0.12 - version: 8.0.14(@types/node@24.12.4) + version: 8.0.14(@types/node@24.12.4)(sass@1.100.0) packages: @@ -256,6 +259,94 @@ packages: '@oxc-project/types@0.132.0': resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + '@parcel/watcher-android-arm64@2.5.6': + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.6': + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.6': + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.6': + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.6': + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.6': + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.6': + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.6': + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.6': + resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.6': + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.6': + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.6': + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.6': + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} + engines: {node: '>= 10.0.0'} + '@react-three/drei@10.7.7': resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==} peerDependencies: @@ -565,6 +656,10 @@ packages: caniuse-lite@1.0.30001793: resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -748,6 +843,9 @@ packages: immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immutable@5.1.6: + resolution: {integrity: sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -933,6 +1031,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-releases@2.0.46: resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} engines: {node: '>=18'} @@ -1071,6 +1172,10 @@ packages: resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -1080,6 +1185,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + sass@1.100.0: + resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==} + engines: {node: '>=20.19.0'} + hasBin: true + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -1515,6 +1625,67 @@ snapshots: '@oxc-project/types@0.132.0': {} + '@parcel/watcher-android-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-x64@2.5.6': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.6': + optional: true + + '@parcel/watcher-win32-arm64@2.5.6': + optional: true + + '@parcel/watcher-win32-ia32@2.5.6': + optional: true + + '@parcel/watcher-win32-x64@2.5.6': + optional: true + + '@parcel/watcher@2.5.6': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.4 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.6 + '@parcel/watcher-darwin-arm64': 2.5.6 + '@parcel/watcher-darwin-x64': 2.5.6 + '@parcel/watcher-freebsd-x64': 2.5.6 + '@parcel/watcher-linux-arm-glibc': 2.5.6 + '@parcel/watcher-linux-arm-musl': 2.5.6 + '@parcel/watcher-linux-arm64-glibc': 2.5.6 + '@parcel/watcher-linux-arm64-musl': 2.5.6 + '@parcel/watcher-linux-x64-glibc': 2.5.6 + '@parcel/watcher-linux-x64-musl': 2.5.6 + '@parcel/watcher-win32-arm64': 2.5.6 + '@parcel/watcher-win32-ia32': 2.5.6 + '@parcel/watcher-win32-x64': 2.5.6 + optional: true + '@react-three/drei@10.7.7(@react-three/fiber@9.6.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(three@0.184.0))(@types/react@19.2.15)(@types/three@0.184.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(three@0.184.0)': dependencies: '@babel/runtime': 7.29.7 @@ -1763,10 +1934,10 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.6 - '@vitejs/plugin-react@6.0.2(vite@8.0.14(@types/node@24.12.4))': + '@vitejs/plugin-react@6.0.2(vite@8.0.14(@types/node@24.12.4)(sass@1.100.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.14(@types/node@24.12.4) + vite: 8.0.14(@types/node@24.12.4)(sass@1.100.0) acorn-jsx@5.3.2(acorn@8.16.0): dependencies: @@ -1814,6 +1985,10 @@ snapshots: caniuse-lite@1.0.30001793: {} + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + convert-source-map@2.0.0: {} cross-env@7.0.3: @@ -1986,6 +2161,8 @@ snapshots: immediate@3.0.6: {} + immutable@5.1.6: {} + imurmurhash@0.1.4: {} install@0.13.0: {} @@ -2120,6 +2297,9 @@ snapshots: natural-compare@1.4.0: {} + node-addon-api@7.1.1: + optional: true + node-releases@2.0.46: {} npm@11.16.0: {} @@ -2179,6 +2359,8 @@ snapshots: react@19.2.6: {} + readdirp@5.0.0: {} + require-from-string@2.0.2: {} rolldown@1.0.2: @@ -2202,6 +2384,14 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.2 '@rolldown/binding-win32-x64-msvc': 1.0.2 + sass@1.100.0: + dependencies: + chokidar: 5.0.0 + immutable: 5.1.6 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.6 + scheduler@0.27.0: {} semver@6.3.1: {} @@ -2314,7 +2504,7 @@ snapshots: uuid@14.0.0: {} - vite@8.0.14(@types/node@24.12.4): + vite@8.0.14(@types/node@24.12.4)(sass@1.100.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -2324,6 +2514,7 @@ snapshots: optionalDependencies: '@types/node': 24.12.4 fsevents: 2.3.3 + sass: 1.100.0 webgl-constants@1.1.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..248c5b2 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + '@parcel/watcher': set this to true or false diff --git a/src/App.css b/src/App.scss similarity index 100% rename from src/App.css rename to src/App.scss diff --git a/src/App.tsx b/src/App.tsx index 482ce40..497805e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,12 @@ import { ThreeView } from './components/ThreeView'; -import './App.css'; +import './App.scss'; import { Toolbar } from './components/Toolbar'; export const App = function () { return ( <> - + ) } diff --git a/src/components/CharacterView.tsx b/src/components/CharacterView.tsx index 32d1f82..45ca7b2 100644 --- a/src/components/CharacterView.tsx +++ b/src/components/CharacterView.tsx @@ -1,8 +1,7 @@ import { observer } from "mobx-react-lite"; -import { state } from "../state"; +import type { Character, Scene } from "../types"; -export const CharacterView = observer(function () { - const character = state.world.currentScene.character; +export const CharacterView = observer(function ({ character }: { character: Character }) { return diff --git a/src/components/GameView.tsx b/src/components/GameView.tsx new file mode 100644 index 0000000..e62f6c0 --- /dev/null +++ b/src/components/GameView.tsx @@ -0,0 +1,17 @@ +import { observer } from "mobx-react-lite"; +import { SceneView } from "./SceneView"; +import { state } from "../state"; +import { useFrame } from "@react-three/fiber"; + +export const GameView = observer(function () { + const game = state.game; + + useFrame((_, delta) => { + state.game?.tick(delta); + }); + + if (!game) + return null; + + return ; +}); diff --git a/src/components/SceneEditorView.tsx b/src/components/SceneEditorView.tsx new file mode 100644 index 0000000..8aa7fa4 --- /dev/null +++ b/src/components/SceneEditorView.tsx @@ -0,0 +1,47 @@ +import { useLayoutEffect, useRef } from 'react'; +import type React from 'react'; +import { useThree } from '@react-three/fiber'; +import { OrbitControls } from '@react-three/drei'; +import { observer } from 'mobx-react-lite'; +import { state } from '../state'; +import { SceneView } from './SceneView'; +import type { Pos3 } from '../types/3d'; + +const CameraSync = observer(function ({ camera }: { camera: Pos3 }) { + const { camera: threeCamera } = useThree(); + + useLayoutEffect(() => { + threeCamera.position.set(camera.position[0], camera.position[1], camera.position[2]); + threeCamera.rotation.set(camera.look[0], camera.look[1], camera.look[2]); + threeCamera.updateProjectionMatrix(); + }); + + return null; +}); + +export const SceneEditorView = observer(function () { + const controlsRef = useRef>(null); + + const handleEnd = () => { + const controls = controlsRef.current; + if (!controls) + return; + const [x, y, z] = controls.object.rotation.toArray(); + state.worldEditor.setCamera({ + position: controls.object.position.toArray(), + look: [x, y, z], + }); + }; + + return (<> + + + + ); +}); diff --git a/src/components/SceneView.tsx b/src/components/SceneView.tsx index 3013c45..14bfd2d 100644 --- a/src/components/SceneView.tsx +++ b/src/components/SceneView.tsx @@ -13,6 +13,6 @@ export const SceneView = observer(function ({ scene }: SceneViewProps) { {Object.values(scene.objects).map((obj) => )} - + ); }); diff --git a/src/components/ThreeView.tsx b/src/components/ThreeView.tsx index bc631d6..9c31116 100644 --- a/src/components/ThreeView.tsx +++ b/src/components/ThreeView.tsx @@ -1,17 +1,37 @@ import { Canvas } from '@react-three/fiber'; import { observer } from 'mobx-react-lite'; import { state } from '../state'; -import { WorldView } from './WorldView'; +import { GameView } from './GameView'; +import { SceneEditorView } from './SceneEditorView'; + +const IconStop = () => ; +const IconPause = () => ; +const IconPlay = () => ; export const ThreeView = observer(function () { + const isGame = state.isGamePlaying; return ( -
+
state.worldEditor.resetSelectedObject()} > - + {isGame ? : } +
+ { + state.game + ? <> + + { + state.game!.isPaused + ? + : + } + + : + } +
) }); diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 95afdac..faa61d1 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -14,18 +14,6 @@ export const Toolbar = observer(function () { } return
- {state.worldEditor.isEnabled &&
EDITOR MODE
} - {state.world.isPlaying - ? <> - - { - (state.world.data.state as RunningGameState).paused - ? - : - } - - : - }
diff --git a/src/components/WorldView.tsx b/src/components/WorldView.tsx deleted file mode 100644 index 9c5e362..0000000 --- a/src/components/WorldView.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useLayoutEffect, useRef } from 'react'; -import type React from 'react'; -import { useFrame, useThree } from '@react-three/fiber'; -import { OrbitControls } from '@react-three/drei'; -import { observer } from 'mobx-react-lite'; -import { state } from '../state'; -import { SceneView } from './SceneView'; - -const CameraSync = observer(function () { - const { camera } = useThree(); - const cam = state.world.currentCamera; // MobX tracks this; re-renders on change - - useLayoutEffect(() => { - camera.position.set(cam.position[0], cam.position[1], cam.position[2]); - camera.rotation.set(cam.look[0], cam.look[1], cam.look[2]); - camera.updateProjectionMatrix(); - }); - - return null; -}); - -export const WorldView = observer(function () { - const world = state.world; - const controlsRef = useRef>(null); - - useFrame((_, delta) => { - world.tick(delta); - }); - - const handleEnd = () => { - const controls = controlsRef.current; - if (!controls || world.isPlaying) - return; - const [x, y, z] = controls.object.rotation.toArray(); - state.worldEditor.setCamera({ - position: controls.object.position.toArray(), - look: [x, y, z], - }); - }; - - return (<> - - - - ) -}); diff --git a/src/index.css b/src/index.scss similarity index 67% rename from src/index.css rename to src/index.scss index 5fb3313..b02ea1a 100644 --- a/src/index.css +++ b/src/index.scss @@ -15,7 +15,8 @@ --heading: system-ui, 'Segoe UI', Roboto, sans-serif; --mono: ui-monospace, Consolas, monospace; - font: 18px/145% var(--sans); + font: 16px/100% var(--sans); + line-height: 1; letter-spacing: 0.18px; color-scheme: light dark; color: var(--text); @@ -24,10 +25,6 @@ text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - - @media (max-width: 1024px) { - font-size: 16px; - } } @media (prefers-color-scheme: dark) { @@ -66,46 +63,17 @@ body { margin: 0; } -h1, -h2 { - font-family: var(--heading); - font-weight: 500; - color: var(--text-h); +svg { + fill: currentColor; + display: inline-block; + height: 1em; + top: 0.125em; + vertical-align: bottom; } -h1 { - font-size: 56px; - letter-spacing: -1.68px; - margin: 32px 0; - @media (max-width: 1024px) { - font-size: 36px; - margin: 20px 0; - } -} -h2 { - font-size: 24px; - line-height: 118%; - letter-spacing: -0.24px; - margin: 0 0 8px; - @media (max-width: 1024px) { - font-size: 20px; - } -} -p { - margin: 0; -} - -code, -.counter { - font-family: var(--mono); - display: inline-flex; - border-radius: 4px; - color: var(--text-h); -} - -code { - font-size: 15px; - line-height: 135%; - padding: 4px 8px; - background: var(--code-bg); -} +button, +input, +select, +textarea { + font: inherit; +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index ee85237..0d14946 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,7 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { App } from './App.tsx' -import './index.css' +import './index.scss' createRoot(document.getElementById('root')!).render( diff --git a/src/state/characterState.ts b/src/state/characterState.ts deleted file mode 100644 index ebfdb0c..0000000 --- a/src/state/characterState.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { makeAutoObservable } from "mobx"; -import type { WorldState } from "./worldState"; -import type { CameraProps } from "@react-three/fiber"; - -export class CharacterState { - private readonly world: WorldState; - - constructor(world: WorldState) { - this.world = world; - makeAutoObservable( - this, - { - }, - ); - } - - public get camera(): CameraProps { - const cam = this.world.currentCamera; - return { - position: cam.position, - fov: 50, - rotation: cam.look, - } - } -} \ No newline at end of file diff --git a/src/state/gameState.ts b/src/state/gameState.ts new file mode 100644 index 0000000..311511e --- /dev/null +++ b/src/state/gameState.ts @@ -0,0 +1,61 @@ +import { makeAutoObservable } from "mobx"; +import type { WorldState } from "./worldState"; +import type { RunningGameState, Scene } from "../types"; +import { clone } from "../utils"; +import { state } from "./rootState"; +import type { Pos3 } from "../types/3d"; +import type { CameraProps } from "@react-three/fiber"; + +export class GameState { + private readonly world: WorldState; + + constructor(world: WorldState) { + this.world = world; + makeAutoObservable(this); + } + + public get state(): RunningGameState { + return this.world.data.state as RunningGameState; + } + + public get isPaused(): boolean { + return this.state.paused; + } + + public get scene(): Scene { + return this.state.scene; + } + + public get camera(): Pos3 { + return this.scene.character; + } + + public get cameraAsThree(): CameraProps { + const cam = this.camera; + return { + position: cam.position, + fov: 50, + rotation: cam.look, + } + } + + public resume(): void { + const state = clone(this.world.data.state) as RunningGameState; + state.paused = false; + this.world.data.state = state; + } + + public pause(): void { + const state = clone(this.world.data.state) as RunningGameState; + state.paused = true; + this.world.data.state = state; + } + + public stop(): void { + this.world.data.state = { playing: false }; + } + + public tick(deltaTime: number): void { + //TODO + } +} \ No newline at end of file diff --git a/src/state/rootState.ts b/src/state/rootState.ts index cb44001..19ccfba 100644 --- a/src/state/rootState.ts +++ b/src/state/rootState.ts @@ -1,6 +1,7 @@ import { makeAutoObservable } from "mobx"; import { WorldState } from "./worldState"; import { WorldEditorState } from "./worldEditorState"; +import { GameState } from "./gameState"; export class RootState { public readonly world = new WorldState(); @@ -16,6 +17,15 @@ export class RootState { }, ); } + + public get isGamePlaying(): boolean { + return this.world.data.state.playing; + } + + public get game(): GameState | undefined { + if (this.isGamePlaying) + return new GameState(this.world); + } } export const state = new RootState(); diff --git a/src/state/worldEditorState.ts b/src/state/worldEditorState.ts index c3a5a42..089ec51 100644 --- a/src/state/worldEditorState.ts +++ b/src/state/worldEditorState.ts @@ -4,6 +4,7 @@ import type { ObjectInstance, Scene, World } from "../types"; import { createObjectInstance } from "../utils/object"; import { randomId } from "../utils"; import type { Pos3, R3, V3 } from "../types/3d"; +import { state } from "./rootState"; export const SelectionEditModeEnum = [ 'translate', @@ -34,7 +35,7 @@ export class WorldEditorState { } public get isEnabled(): boolean { - return !this.world.isPlaying; + return !state.isGamePlaying; } public setCamera(value: Pos3): void { diff --git a/src/state/worldState.ts b/src/state/worldState.ts index 6bde9ec..06dd023 100644 --- a/src/state/worldState.ts +++ b/src/state/worldState.ts @@ -1,19 +1,15 @@ import { makeAutoObservable } from "mobx"; import { WorldFactory } from "../model/worldFactory"; -import type { ObjectType, RunningGameState, Scene, World } from "../types"; -import { CharacterState } from "./characterState"; -import type { Pos3 } from "../types/3d"; -import { clone } from "../utils"; +import type { ObjectType, World } from "../types"; import type { VoxelType } from "../types/voxel"; import { state } from "./rootState"; import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes"; import { wolf } from "../model/objectPrefabs/wolf"; +import { clone } from "../utils"; export class WorldState { public data: World = WorldFactory.create(); - public character = new CharacterState(this); - constructor() { makeAutoObservable(this); } @@ -75,60 +71,6 @@ export class WorldState { WorldFactory.save(this.data); } - public get isPlaying(): boolean { - return this.data.state.playing; - } - - public get currentScene(): Scene { - return this.isPlaying - ? (this.data.state as RunningGameState).scene - : this.data.initialScene; - } - - public get currentCamera(): Pos3 { - return this.isPlaying - ? (this.data.state as RunningGameState).scene.character - : this.data.editorCamera; - } - - public tick(_delta: number) { - if (!this.isPlaying) - return; - - //TODO - } - - public play(): void { - if (this.isPlaying) { - const state = clone(this.data.state) as RunningGameState; - state.paused = false; - this.data.state = state; - } - else { - this.data.state = { - playing: true, - paused: false, - time: 0, - scene: clone(this.data.initialScene), - } - } - state.worldEditor.resetSelectedObject(); - } - - public pause(): void { - if (!this.isPlaying) - return; - const state = clone(this.data.state) as RunningGameState; - state.paused = true; - this.data.state = state; - } - - public stop(): void { - if (!this.isPlaying) - return; - this.data.state = { playing: false }; - } - public getObjectTypeById(id: string): ObjectType | undefined { return this.data.objectTypes[id]; } @@ -136,4 +78,18 @@ export class WorldState { public getVoxelTypeById(id: string): VoxelType | undefined { return this.data.voxelTypes[id]; } + + public play(): void { + if (state.game) + state.game.resume(); + else { + this.data.state = { + playing: true, + paused: false, + time: 0, + scene: clone(this.data.initialScene), + } + state.worldEditor.resetSelectedObject(); + } + } } \ No newline at end of file diff --git a/src/types/character.ts b/src/types/character.ts new file mode 100644 index 0000000..000f940 --- /dev/null +++ b/src/types/character.ts @@ -0,0 +1,4 @@ +import type { Pos3 } from "./3d"; + +export type Character = Pos3 & { +} diff --git a/src/types/characterState.ts b/src/types/characterState.ts deleted file mode 100644 index 92f1bb2..0000000 --- a/src/types/characterState.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { Pos3 } from "./3d"; - -export type CharacterState = Pos3 & { -} diff --git a/src/types/index.ts b/src/types/index.ts index afede59..005ebad 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,7 +3,7 @@ export * from './scene'; export * from './world'; export * from './gameRules'; export * from './gameState'; -export * from './characterState'; +export * from './character'; diff --git a/src/types/scene.ts b/src/types/scene.ts index 88b5c1f..afe918a 100644 --- a/src/types/scene.ts +++ b/src/types/scene.ts @@ -1,7 +1,7 @@ -import type { CharacterState } from "./characterState"; +import type { Character } from "./character"; import type { ObjectInstance } from "./object"; export type Scene = { - character: CharacterState; + character: Character; objects: Record; } \ No newline at end of file