diff --git a/webclient/src/components/CharacterView.tsx b/webclient/src/components/CharacterView.tsx index d40422e..a0abf45 100644 --- a/webclient/src/components/CharacterView.tsx +++ b/webclient/src/components/CharacterView.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react-lite"; -import type { ObjectType, RuntimeGameObjectInstance } from "../types"; +import type { ObjectType, ObjectInstance } from "../types"; import { ObjectViewInternal, type ObjectViewInternalHandle } from "./ObjectViewInternal"; import { useEffect, useLayoutEffect, useMemo, useRef } from "react"; import { Euler, Quaternion, Vector3 } from "three"; @@ -25,7 +25,7 @@ const _charPos = new Vector3(); const _lookAt = new Vector3(); type PlayerObjectViewProps = { - object: RuntimeGameObjectInstance; + object: ObjectInstance; objectType: ObjectType; } diff --git a/webclient/src/components/DebugLabelsView.tsx b/webclient/src/components/DebugLabelsView.tsx index 1d84431..5176f25 100644 --- a/webclient/src/components/DebugLabelsView.tsx +++ b/webclient/src/components/DebugLabelsView.tsx @@ -2,6 +2,7 @@ import { useEffect } from "react"; import { observer } from "mobx-react-lite"; import { debugLabelsStore } from "../state/debugLabelsState"; import { DebugLabel } from "./DebugLabel"; +import type { Scene } from "../types"; export const DebugLabelsView = observer(function ({ scene, editMode }: { scene: Scene, editMode?: boolean }) { useEffect( diff --git a/webclient/src/components/GameObjectView.tsx b/webclient/src/components/GameObjectView.tsx index 2f7e392..8dafd3f 100644 --- a/webclient/src/components/GameObjectView.tsx +++ b/webclient/src/components/GameObjectView.tsx @@ -2,6 +2,7 @@ import { observer } from "mobx-react-lite"; import { state } from "../state"; import { ObjectViewInternal } from "./ObjectViewInternal"; import { PlayerObjectView } from "./CharacterView"; +import type { ObjectInstance } from "../types"; export type GameObjectViewProps = { object: ObjectInstance; diff --git a/webclient/src/components/ObjectEditorView.tsx b/webclient/src/components/ObjectEditorView.tsx index afb299f..635afc6 100644 --- a/webclient/src/components/ObjectEditorView.tsx +++ b/webclient/src/components/ObjectEditorView.tsx @@ -94,9 +94,6 @@ export const ObjectEditorView = observer(function ({ object, isPlayer }: ObjectE if (!objectType) return null; - // Track cache so observer re-renders when refreshObjectTypeCaches is called - void object.cache.updatedAt; - function handleClick() { if (state.worldEditor.isEnabled) { // Reading selection state inside an event handler: not tracked by observer. diff --git a/webclient/src/components/ObjectViewInternal.tsx b/webclient/src/components/ObjectViewInternal.tsx index afd0eac..b2d0b3c 100644 --- a/webclient/src/components/ObjectViewInternal.tsx +++ b/webclient/src/components/ObjectViewInternal.tsx @@ -1,27 +1,12 @@ -import type { ObjectInstance, ObjectType, RuntimeGameObjectInstance, RuntimeObjectInstance } from "../types"; -import type { VoxelGroup } from "../types/runtime/voxelGroup"; -import { useEffect, useImperativeHandle, useRef, type Ref, memo, useMemo } from "react"; +import type { ObjectInstance, ObjectType } from "../types"; +import { useEffect, useImperativeHandle, useRef, type Ref } from "react"; import { Euler, Quaternion, Mesh, MeshStandardMaterial, type Group } from "three"; import type { ThreeEvent } from "@react-three/fiber"; -import { ConvexHullCollider, RapierRigidBody } from "@react-three/rapier"; +import { RapierRigidBody } from "@react-three/rapier"; import { SyncRigidBody } from "./SyncRigidBody"; import { reaction, runInAction } from "mobx"; import { v3toRapier } from "../utils"; import { state } from "../state"; -import { buildVoxelGroupMesh } from "../utils/graphics/voxelMesh"; - -const VoxelGroupMesh = memo(function VoxelGroupMesh({ vg, opacity }: { vg: VoxelGroup, opacity: number }) { - const geo = useMemo(() => buildVoxelGroupMesh(vg.positions), [vg]); - useEffect(() => () => { geo.dispose(); }, [geo]); - - const op = vg.opacity * opacity; - - return ( - - - - ); -}); export type ObjectViewInternalHandle = { group: Group | null; @@ -71,8 +56,6 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props [object, object.id, props.isPlayer] ); - const gameObj = object as RuntimeGameObjectInstance; - useEffect( () => { return reaction( @@ -98,8 +81,8 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props useEffect( () => { - runInAction(() => { gameObj.rb = () => rbRef.current; }); - return () => { runInAction(() => { gameObj.rb = () => null; }); }; + runInAction(() => { object.rb = () => rbRef.current; }); + return () => { runInAction(() => { object.rb = () => null; }); }; }, // eslint-disable-next-line react-hooks/exhaustive-deps [object.id], @@ -130,9 +113,11 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props return; const otherObj = Object.values(game.scene.objects) .find((o) => o.rb()?.handle === otherHandle); - game.emitEvent('objectTouchStart', { object: gameObj, other: otherObj }); + + const context = { object: { ...object, typeId: objectType.id }, other: otherObj }; + game.emitEvent('objectTouchStart', context); if (otherObj?.id === game.scene.playerObjectId) - game.emitEvent('playerTouchStart', { object: gameObj, other: otherObj }); + game.emitEvent('playerTouchStart', context); }} onSync={(data) => { runInAction(() => { diff --git a/webclient/src/components/SceneEditorView.tsx b/webclient/src/components/SceneEditorView.tsx index 16443a4..f576607 100644 --- a/webclient/src/components/SceneEditorView.tsx +++ b/webclient/src/components/SceneEditorView.tsx @@ -4,7 +4,7 @@ import { OrbitControls } from '@react-three/drei'; import { observer } from 'mobx-react-lite'; import { state } from '../state'; import { SceneView } from './SceneView'; -import { MOUSE, type OrthographicCamera, type PerspectiveCamera } from 'three'; +import { MOUSE } from 'three'; import { useSyncCamera } from '../utils/hooks/useSyncCamera'; import { Physics } from '@react-three/rapier'; diff --git a/webclient/src/components/SceneView.tsx b/webclient/src/components/SceneView.tsx index 4bc85fa..35c9611 100644 --- a/webclient/src/components/SceneView.tsx +++ b/webclient/src/components/SceneView.tsx @@ -1,11 +1,11 @@ import { observer } from "mobx-react-lite"; -import type { RuntimeScene } from "../types"; import { GameObjectView } from "./GameObjectView"; import { ObjectEditorView } from "./ObjectEditorView"; import { DebugLabelsView } from "./DebugLabelsView"; +import type { Scene } from "../types"; type SceneViewProps = { - scene: RuntimeScene, + scene: Scene, isEditor?: boolean; debugOverlay?: boolean; } @@ -18,7 +18,7 @@ export const SceneView = observer(function (props: SceneViewProps) { Object.values(props.scene.objects) .map((obj) => ( !props.isEditor - ? + ? : )) } diff --git a/webclient/src/components/ThreeView.tsx b/webclient/src/components/ThreeView.tsx index 0f0e576..193c3aa 100644 --- a/webclient/src/components/ThreeView.tsx +++ b/webclient/src/components/ThreeView.tsx @@ -8,7 +8,6 @@ import { SceneEditorView } from './SceneEditorView'; import { JoystickView } from './JoystickView'; import { useEffect } from 'react'; import { runInAction } from 'mobx'; -import { IconPlayerPlayFilled, IconPlayerPauseFilled, IconPlayerStopFilled, IconPlayerSkipForwardFilled } from '@tabler/icons-react'; export const ThreeView = observer(function () { const isGame = !!state.game; @@ -39,23 +38,7 @@ export const ThreeView = observer(function () { {isGame ? : } {isGame && } -
- { - state.game - ? <> - - { - state.game!.isPaused - ? <> - - - - : - } - - : - } -
+ ); diff --git a/webclient/src/components/layers/LayeredThreeView.tsx b/webclient/src/components/layers/LayeredThreeView.tsx index c87b755..96e2f7b 100644 --- a/webclient/src/components/layers/LayeredThreeView.tsx +++ b/webclient/src/components/layers/LayeredThreeView.tsx @@ -6,6 +6,7 @@ import { SceneEditorObjectSelectionLayer } from "./SceneEditor/SceneEditorObject import { LightsLayer } from "./LighsLayer"; import { StatsLayer } from './StatsLayer'; import { SceneEditorObjectGizmosLayer } from "./SceneEditor/SceneEditorObjectGizmosLayer"; +import { PlayButtonsLayer } from "./PlayButtonsLayer"; export const LayeredThreeView = observer(function () { @@ -20,5 +21,6 @@ export const LayeredThreeView = observer(function () { + ; }); diff --git a/webclient/src/components/layers/PlayButtonsLayer.tsx b/webclient/src/components/layers/PlayButtonsLayer.tsx new file mode 100644 index 0000000..30339b5 --- /dev/null +++ b/webclient/src/components/layers/PlayButtonsLayer.tsx @@ -0,0 +1,23 @@ +import { observer } from "mobx-react-lite"; +import { state } from "../../state"; +import { IconPlayerPlayFilled, IconPlayerPauseFilled, IconPlayerStopFilled, IconPlayerSkipForwardFilled } from '@tabler/icons-react'; + +export const PlayButtonsLayer = observer(function () { + return
+ { + state.game + ? <> + + { + state.game!.isPaused + ? <> + + + + : + } + + : + } +
; +}); diff --git a/webclient/src/model/gameFactory.ts b/webclient/src/model/gameFactory.ts index 2c77087..e326fae 100644 --- a/webclient/src/model/gameFactory.ts +++ b/webclient/src/model/gameFactory.ts @@ -1,7 +1,6 @@ import { toJS } from "mobx"; import type { Game, Scene, ScriptAction, ScriptCoroutineContext, World } from "../types"; import { clone } from "../utils"; -import { populateRuntimeScene } from "../utils/runtime"; import { ScriptGameEventBus } from "../scripts/eventBus"; import * as ScriptActions from "../scripts/actions"; import { positionOfObject } from "../scripts/positionOfObject"; @@ -19,7 +18,7 @@ import type { ScriptCoroutines } from "../scripts/coroutines"; export class GameFactory { public static create(world: World): Game { - const scene = populateRuntimeScene(clone(world.initialScene), world); + const scene = clone(world.initialScene); const game = { paused: false, diff --git a/webclient/src/model/worldFactory.ts b/webclient/src/model/worldFactory.ts index 257c301..b253894 100644 --- a/webclient/src/model/worldFactory.ts +++ b/webclient/src/model/worldFactory.ts @@ -1,11 +1,10 @@ -import { type RuntimeWorld, type World } from "../types"; -import { depopulateRuntimeWorld, populateRuntimeWorld } from "../utils/runtime"; +import { type World } from "../types"; import { DEFAULT_VOXEL_TYPES } from "./defaultVoxelTypes"; export class WorldFactory { - public static create(): RuntimeWorld { - return populateRuntimeWorld({ + public static create(): World { + return { objectTypes: {}, voxelTypes: DEFAULT_VOXEL_TYPES, initialScene: { @@ -19,17 +18,17 @@ export class WorldFactory { position: [0, 5, 10], look: [0, 0, 0], }, - }); + }; } - public static load(): RuntimeWorld | undefined { + public static load(): World | undefined { const json = localStorage.getItem("world"); if (json) { - return populateRuntimeWorld(JSON.parse(json) as World); + return JSON.parse(json) as World; } } - public static save(world: RuntimeWorld): void { - localStorage.setItem("world", JSON.stringify(depopulateRuntimeWorld(world))); + public static save(world: World): void { + localStorage.setItem("world", JSON.stringify(world)); } } diff --git a/webclient/src/scripts/positionOfObject.ts b/webclient/src/scripts/positionOfObject.ts index 8f28510..e65acbc 100644 --- a/webclient/src/scripts/positionOfObject.ts +++ b/webclient/src/scripts/positionOfObject.ts @@ -1,6 +1,6 @@ -import type { RuntimeGameObjectInstance, V3 } from "../types"; +import type { ObjectInstance, V3 } from "../types"; -export function positionOfObject(obj: RuntimeGameObjectInstance): V3 { +export function positionOfObject(obj: ObjectInstance): V3 { const t = obj.rb()?.translation(); const result: V3 = t ? [t.x, t.y, t.z] diff --git a/webclient/src/state/worldEditorState.ts b/webclient/src/state/worldEditorState.ts index 7af0be5..21ed093 100644 --- a/webclient/src/state/worldEditorState.ts +++ b/webclient/src/state/worldEditorState.ts @@ -4,7 +4,6 @@ import { type ObjectInstance, type Pos3, type R3, type V3, type ObjectType, type import { createObjectInstance } from "../utils/object"; import { randomId } from "../utils"; import { state } from "./rootState"; -import { populateRuntimeObject } from "../utils/runtime"; import { DEFAULT_VOXEL_TYPE } from "../model/defaultVoxelTypes"; export const ObjectEditModeEnum = [ @@ -130,7 +129,7 @@ export class WorldEditorState { typeId: string, pos: { x: number; y: number; z: number; }, ): ObjectInstance { - const obj = populateRuntimeObject(createObjectInstance(randomId(), typeId), this.world.data); + const obj = createObjectInstance(randomId(), typeId); obj.position = [pos.x, pos.y, pos.z]; obj.rotation = [0, 0, 0]; diff --git a/webclient/src/state/worldState.ts b/webclient/src/state/worldState.ts index 4a87294..d2c53f2 100644 --- a/webclient/src/state/worldState.ts +++ b/webclient/src/state/worldState.ts @@ -5,7 +5,6 @@ import { state } from "./rootState"; import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes"; import { wolf } from "../model/objectPrefabs/wolf"; import { terrainXZ } from "../model/objectPrefabs/terrain"; -import { populateRuntimeWorld } from "../utils/runtime"; export class WorldState { public data: World = WorldFactory.create(); @@ -58,7 +57,7 @@ export class WorldState { objects.map((obj) => [obj.id, obj]), ) as Record; - this.data = populateRuntimeWorld({ + this.data = { objectTypes: { wolf: { id: 'wolf', @@ -97,7 +96,7 @@ export class WorldState { gameRules: { gravity: true, }, - }); + }; console.log(objects); state.worldEditor.resetSelection(); diff --git a/webclient/src/types/scripts/eventBus.ts b/webclient/src/types/scripts/eventBus.ts index d352adc..d19bb47 100644 --- a/webclient/src/types/scripts/eventBus.ts +++ b/webclient/src/types/scripts/eventBus.ts @@ -1,4 +1,4 @@ -import type { ObjectInstance, ObjectType, RuntimeGameObjectInstance, Scene, World } from "../model"; +import type { ObjectInstance, ObjectType, Scene, World } from "../model"; import type { ScriptAction } from "./actions"; export type ScriptCoroutine = Generator; @@ -9,15 +9,15 @@ export type NamedScriptCoroutine = { } export type ScriptCoroutineContext = { - thisObject: RuntimeGameObjectInstance, + thisObject: ObjectInstance, thisObjectType: ObjectType; }; export type ScriptContext = ScriptCoroutineContext & { world: World; scene: Scene; - object?: RuntimeGameObjectInstance; - other?: RuntimeGameObjectInstance; + object?: ObjectInstance; + other?: ObjectInstance; } export type ScriptGameEventHandler = (context: ScriptContext) => ScriptCoroutine; diff --git a/webclient/src/utils/object.ts b/webclient/src/utils/object.ts index 661e7e9..ecda024 100644 --- a/webclient/src/utils/object.ts +++ b/webclient/src/utils/object.ts @@ -10,5 +10,8 @@ export function createObjectInstance(id: string, typeId: string): ObjectInstance position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1], + + linearVelocity: [0, 0, 0], + angularVelocity: [0, 0, 0], }; } diff --git a/webclient/src/utils/runtime/index.ts b/webclient/src/utils/runtime/index.ts index 53f97dd..1c19a11 100644 --- a/webclient/src/utils/runtime/index.ts +++ b/webclient/src/utils/runtime/index.ts @@ -1,3 +1 @@ -export * from './world'; -export * from './scene'; export * from './object'; diff --git a/webclient/src/utils/runtime/object.ts b/webclient/src/utils/runtime/object.ts index 44491a3..c6861ac 100644 --- a/webclient/src/utils/runtime/object.ts +++ b/webclient/src/utils/runtime/object.ts @@ -1,6 +1,4 @@ -import { getObjectVoxelGroups } from "../graphics/voxelGroup"; -import type { ObjectInstance, ObjectType, RuntimeObjectInstance, RuntimeObjectType, V3, World } from "../../types"; -import { buildObjectTrimesh } from "../graphics/mesh"; +import type { ObjectType, V3 } from "../../types"; export function computeBoundingBox(objectType: ObjectType): { min: V3; max: V3 } { if (!objectType.voxels.length) @@ -17,35 +15,3 @@ export function computeBoundingBox(objectType: ObjectType): { min: V3; max: V3 } } return { min: [minX, minY, minZ], max: [maxX, maxY, maxZ] }; } - -export function populateRuntimeObjectType(value: ObjectType, world: World): RuntimeObjectType { - return { - ...value, - runtime: { - voxelGroups: getObjectVoxelGroups(value, world.voxelTypes), - colliderMesh: buildObjectTrimesh(value, world.voxelTypes), - boundingBox: computeBoundingBox(value), - updatedAt: 0, - }, - }; -} - -export function populateRuntimeObject(value: ObjectInstance, _world: World): RuntimeObjectInstance { - return { - ...value, - runtime: { - rb: () => null, - }, - }; -} - -export function depopulateRuntimeObjectType(value: RuntimeObjectType, _world: World): ObjectType { - const { runtime: _, ...result } = value; - return result; -} - - -export function depopulateRuntimeObject(value: RuntimeObjectInstance, _world: World): ObjectInstance { - const { runtime: _, ...result } = value; - return result; -} diff --git a/webclient/src/utils/runtime/scene.ts b/webclient/src/utils/runtime/scene.ts deleted file mode 100644 index 802c587..0000000 --- a/webclient/src/utils/runtime/scene.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { RuntimeScene, Scene, World } from "../../types"; -import { depopulateRuntimeObject, populateRuntimeObject } from "./object"; - -export function populateRuntimeScene(scene: Scene, world: World): RuntimeScene { - return { - ...scene, - objects: Object.fromEntries(Object.entries(scene.objects).map(([id, obj]) => [id, populateRuntimeObject(obj, world)])), - }; -} - -export function depopulateRuntimeScene(scene: RuntimeScene, world: World): Scene { - return { - ...scene, - objects: Object.fromEntries(Object.entries(scene.objects).map(([id, obj]) => [id, depopulateRuntimeObject(obj, world)])), - }; -} diff --git a/webclient/src/utils/runtime/world.ts b/webclient/src/utils/runtime/world.ts deleted file mode 100644 index f509a99..0000000 --- a/webclient/src/utils/runtime/world.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { RuntimeWorld, World } from "../../types"; -import { depopulateRuntimeScene, populateRuntimeScene } from "./scene"; - -export function populateRuntimeWorld(world: World): RuntimeWorld { - return { - ...world, - initialScene: populateRuntimeScene(world.initialScene, world), - }; -} - -export function depopulateRuntimeWorld(world: RuntimeWorld): World { - return { - ...world, - initialScene: depopulateRuntimeScene(world.initialScene, world), - }; -}