replaced characer with any object can be controlled by player
This commit is contained in:
parent
b3ed15fa30
commit
fa459ba8ec
|
|
@ -13,6 +13,7 @@
|
|||
"@react-three/drei": "^10.7.7",
|
||||
"@react-three/fiber": "^9.6.1",
|
||||
"@react-three/rapier": "^2.2.0",
|
||||
"@tabler/icons-react": "^3.44.0",
|
||||
"@types/three": "^0.184.1",
|
||||
"blockly": "^12.5.1",
|
||||
"install": "^0.13.0",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ importers:
|
|||
'@react-three/rapier':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(@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))(react@19.2.6)(three@0.184.0)
|
||||
'@tabler/icons-react':
|
||||
specifier: ^3.44.0
|
||||
version: 3.44.0(react@19.2.6)
|
||||
'@types/three':
|
||||
specifier: ^0.184.1
|
||||
version: 0.184.1
|
||||
|
|
@ -537,6 +540,14 @@ packages:
|
|||
'@rolldown/pluginutils@1.0.1':
|
||||
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
|
||||
|
||||
'@tabler/icons-react@3.44.0':
|
||||
resolution: {integrity: sha512-8+rvzBbVm/1Z3sG3x7GUNAaxIKxwgz8xaMhRs23nrCnMTKRFAhEC+82zAIFeAA0seXdrAGX5HFCkaLpGK2rVHg==}
|
||||
peerDependencies:
|
||||
react: '>= 16'
|
||||
|
||||
'@tabler/icons@3.44.0':
|
||||
resolution: {integrity: sha512-Wn0AOZG9sg0L+bjfMqq4eNhC6pQjIrk94LvvWYNYkY8KH8wC3YILRzQlrnVJc4FUeMxH/AK97QsYCX35H3LndA==}
|
||||
|
||||
'@tweenjs/tween.js@23.1.3':
|
||||
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
||||
|
||||
|
|
@ -2045,6 +2056,13 @@ snapshots:
|
|||
|
||||
'@rolldown/pluginutils@1.0.1': {}
|
||||
|
||||
'@tabler/icons-react@3.44.0(react@19.2.6)':
|
||||
dependencies:
|
||||
'@tabler/icons': 3.44.0
|
||||
react: 19.2.6
|
||||
|
||||
'@tabler/icons@3.44.0': {}
|
||||
|
||||
'@tweenjs/tween.js@23.1.3': {}
|
||||
|
||||
'@tybys/wasm-util@0.10.2':
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import type { Character } from "../types";
|
||||
import { SyncRigidBody } from "./SyncRigidBody";
|
||||
import { state } from "../state";
|
||||
import type { ObjectType, RuntimeGameObjectInstance } from "../types";
|
||||
import { ObjectViewInternal, type ObjectViewInternalHandle } from "./ObjectViewInternal";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Euler, Quaternion, Vector3 } from "three";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useKeyboardControls } from "@react-three/drei";
|
||||
import { useRapier, useBeforePhysicsStep, type RapierRigidBody, type RapierCollider, RoundCuboidCollider } from "@react-three/rapier";
|
||||
import { useRapier, useBeforePhysicsStep } from "@react-three/rapier";
|
||||
import { joystickValues } from "../joystickInput";
|
||||
import { state } from "../state";
|
||||
|
||||
const SPEED = 5;
|
||||
const JUMP_SPEED = 8;
|
||||
|
|
@ -16,7 +16,6 @@ const SENSITIVITY = 0.002;
|
|||
const SHOULDER_OFFSET = new Vector3(0.1, 1.5, 2.5);
|
||||
const LOOK_RATE = 2000;
|
||||
|
||||
// recreate private types
|
||||
type RapierWorldCreateCharacterControllerFunction = ReturnType<typeof useRapier>['world']['createCharacterController'];
|
||||
type KinematicCharacterController = ReturnType<RapierWorldCreateCharacterControllerFunction>;
|
||||
|
||||
|
|
@ -26,15 +25,13 @@ const _offset = new Vector3();
|
|||
const _charPos = new Vector3();
|
||||
const _lookAt = new Vector3();
|
||||
|
||||
type CharacterViewProps = {
|
||||
character: Character;
|
||||
editMode?: boolean;
|
||||
type PlayerObjectViewProps = {
|
||||
object: RuntimeGameObjectInstance;
|
||||
objectType: ObjectType;
|
||||
}
|
||||
|
||||
export const CharacterView = observer(function ({ character, editMode }: CharacterViewProps) {
|
||||
const pos = character.transform.position;
|
||||
const rbRef = useRef<RapierRigidBody>(null);
|
||||
const colliderRef = useRef<RapierCollider>(null);
|
||||
export const PlayerObjectView = observer(function ({ object, objectType }: PlayerObjectViewProps) {
|
||||
const handleRef = useRef<ObjectViewInternalHandle>(null);
|
||||
const [, get] = useKeyboardControls();
|
||||
const { gl } = useThree();
|
||||
const { world } = useRapier();
|
||||
|
|
@ -59,7 +56,6 @@ export const CharacterView = observer(function ({ character, editMode }: Charact
|
|||
}, [world]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editMode) return;
|
||||
const canvas = gl.domElement;
|
||||
const onClick = () => canvas.requestPointerLock();
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
|
|
@ -73,14 +69,15 @@ export const CharacterView = observer(function ({ character, editMode }: Charact
|
|||
canvas.removeEventListener('click', onClick);
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
};
|
||||
}, [gl, editMode]);
|
||||
}, [gl]);
|
||||
|
||||
useBeforePhysicsStep((world) => {
|
||||
if (editMode) return;
|
||||
const rb = rbRef.current;
|
||||
const collider = colliderRef.current;
|
||||
const rb = handleRef.current?.rb;
|
||||
if (!rb) return;
|
||||
const collider = rb.collider(0);
|
||||
if (!collider) return;
|
||||
const controller = controllerRef.current;
|
||||
if (!rb || !collider || !controller) return;
|
||||
if (!controller) return;
|
||||
if (state.game?.isPaused) return;
|
||||
|
||||
const dt = world.timestep;
|
||||
|
|
@ -118,8 +115,7 @@ export const CharacterView = observer(function ({ character, editMode }: Charact
|
|||
});
|
||||
|
||||
useFrame(({ camera }, delta) => {
|
||||
if (editMode) return;
|
||||
const rb = rbRef.current;
|
||||
const rb = handleRef.current?.rb;
|
||||
if (!rb) return;
|
||||
|
||||
mouseRef.current.x += joystickValues.look.x * LOOK_RATE * delta;
|
||||
|
|
@ -139,23 +135,11 @@ export const CharacterView = observer(function ({ character, editMode }: Charact
|
|||
});
|
||||
|
||||
return (
|
||||
<SyncRigidBody
|
||||
ref={rbRef}
|
||||
type="kinematicPosition"
|
||||
colliders={false}
|
||||
position={[pos[0] + 0.5, pos[1] + 0.5, pos[2] + 0.5]}
|
||||
onSync={() => { }}
|
||||
>
|
||||
{/* <BallCollider ref={colliderRef} args={[0.55]} /> */}
|
||||
{/* <CapsuleCollider ref={colliderRef} args={[0.4, 0.5]} /> */}
|
||||
{/* <CuboidCollider ref={colliderRef} args={[0.55, 0.4, 0.55]} /> */}
|
||||
<RoundCuboidCollider ref={colliderRef} args={[0.4, 0.4, 0.4, 0.1]} />
|
||||
<group>
|
||||
<mesh rotation={[-Math.PI / 2, -Math.PI / 4, 0]}>
|
||||
<coneGeometry args={[0.55, 0.8, 4]} />
|
||||
<meshStandardMaterial color="yellow" />
|
||||
</mesh>
|
||||
</group>
|
||||
</SyncRigidBody>
|
||||
<ObjectViewInternal
|
||||
ref={handleRef}
|
||||
object={object}
|
||||
objectType={objectType}
|
||||
isPlayer
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import type { ObjectInstance, Runtime } from "../types";
|
||||
import type { ObjectInstance, Runtime, RuntimeGameObjectInstance } from "../types";
|
||||
import { state } from "../state";
|
||||
import { ObjectViewInternal } from "./ObjectViewInternal";
|
||||
import { PlayerObjectView } from "./CharacterView";
|
||||
|
||||
export type GameObjectViewProps = {
|
||||
object: Runtime<ObjectInstance>;
|
||||
isPlayer: boolean;
|
||||
}
|
||||
|
||||
export const GameObjectView = observer(function (props: GameObjectViewProps) {
|
||||
|
||||
const objectType = state.world.getObjectTypeById(props.object.typeId);
|
||||
if (!objectType)
|
||||
return null;
|
||||
|
||||
return <ObjectViewInternal {...props} objectType={objectType} />
|
||||
if (props.isPlayer)
|
||||
return <PlayerObjectView object={props.object as RuntimeGameObjectInstance} objectType={objectType} />;
|
||||
|
||||
return <ObjectViewInternal {...props} objectType={objectType} />;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
display: block;
|
||||
display: flex;
|
||||
padding: 8px 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
|
|
@ -28,6 +28,10 @@
|
|||
background: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&>.title {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
& details {
|
||||
|
|
|
|||
|
|
@ -14,13 +14,30 @@ export const MenuNodeView = observer(function ({ node }: { node: MenuNode }) {
|
|||
ref.current.open = true;
|
||||
}, [forceOpen]);
|
||||
|
||||
const classNames = [];
|
||||
if (node.selected?.())
|
||||
classNames.push('selected');
|
||||
if (node.className !== undefined)
|
||||
classNames.push(node.className);
|
||||
|
||||
return (
|
||||
<details ref={ref}>
|
||||
<summary
|
||||
className={node.selected?.() ? 'selected' : ''}
|
||||
className={classNames.join(' ')}
|
||||
onClick={() => node.onClick?.()}
|
||||
>
|
||||
{node.title}
|
||||
<div className="title">{node.title}</div>
|
||||
{node.actions && <div className="actions">
|
||||
{node.actions.map((action) =>
|
||||
<div
|
||||
key={action.id}
|
||||
title={action.tooltip}
|
||||
onClick={(e) => { e.stopPropagation(); e.preventDefault(); action.onClick(); }}
|
||||
>
|
||||
{action.content}
|
||||
</div>
|
||||
)}
|
||||
</div>}
|
||||
</summary>
|
||||
{node.children?.map((child) => <MenuNodeView key={child.id} node={child} />)}
|
||||
</details>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { ObjectViewInternal, type ObjectViewInternalHandle } from "./ObjectViewI
|
|||
|
||||
type ObjectEditorViewProps = {
|
||||
object: Runtime<ObjectInstance>;
|
||||
isPlayer: boolean;
|
||||
}
|
||||
|
||||
type SelectionOverlayProps = {
|
||||
|
|
@ -50,7 +51,7 @@ const SelectionOverlay = observer(function ({ objectId, ref, onTransformEnd }: S
|
|||
);
|
||||
});
|
||||
|
||||
export const ObjectEditorView = observer(function ({ object }: ObjectEditorViewProps) {
|
||||
export const ObjectEditorView = observer(function ({ object, isPlayer }: ObjectEditorViewProps) {
|
||||
const dataRef = useRef<ObjectViewInternalHandle>(null);
|
||||
|
||||
// Only observes world object types — not selection state — so won't
|
||||
|
|
@ -91,6 +92,7 @@ export const ObjectEditorView = observer(function ({ object }: ObjectEditorViewP
|
|||
ref={dataRef}
|
||||
object={object}
|
||||
isEditor
|
||||
isPlayer={isPlayer}
|
||||
objectType={objectType}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ type ObjectViewInternalProps = {
|
|||
object: Omit<RuntimeObjectInstance, 'typeId'>;
|
||||
objectType: ObjectType;
|
||||
isEditor?: boolean;
|
||||
isPlayer?: boolean;
|
||||
onClick?: (e: ThreeEvent<MouseEvent>) => void;
|
||||
ref?: Ref<ObjectViewInternalHandle>;
|
||||
}
|
||||
|
|
@ -28,30 +29,30 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props
|
|||
useImperativeHandle(ref, () => ({ group: groupRef.current, rb: rbRef.current }));
|
||||
|
||||
useEffect(
|
||||
() => reaction(
|
||||
() => 'version' in object ? object.version : 0,
|
||||
() => {
|
||||
if (!rbRef.current)
|
||||
return;
|
||||
() => {
|
||||
if (props.isPlayer) return;
|
||||
return reaction(
|
||||
() => 'version' in object ? object.version : 0,
|
||||
() => {
|
||||
if (!rbRef.current)
|
||||
return;
|
||||
|
||||
const gameObj = object as RuntimeGameObjectInstance;
|
||||
const gameObj = object as RuntimeGameObjectInstance;
|
||||
|
||||
// position
|
||||
rbRef.current.setTranslation(v3toRapier(gameObj.position), true);
|
||||
rbRef.current.setTranslation(v3toRapier(gameObj.position), true);
|
||||
|
||||
// rotation
|
||||
const euler = new Euler(gameObj.rotation[0], gameObj.rotation[1], gameObj.rotation[2]);
|
||||
const q = new Quaternion().setFromEuler(euler);
|
||||
rbRef.current.setRotation({ x: q.x, y: q.y, z: q.z, w: q.w }, true);
|
||||
const euler = new Euler(gameObj.rotation[0], gameObj.rotation[1], gameObj.rotation[2]);
|
||||
const q = new Quaternion().setFromEuler(euler);
|
||||
rbRef.current.setRotation({ x: q.x, y: q.y, z: q.z, w: q.w }, true);
|
||||
|
||||
// scale
|
||||
groupRef.current?.scale.set(...gameObj.scale);
|
||||
groupRef.current?.scale.set(...gameObj.scale);
|
||||
|
||||
rbRef.current.setLinvel(v3toRapier(gameObj.linearVelocity), true);
|
||||
rbRef.current.setLinvel(v3toRapier(gameObj.linearVelocity), true);
|
||||
|
||||
rbRef.current.setAngvel(v3toRapier(gameObj.angularVelocity), true);
|
||||
},
|
||||
),
|
||||
rbRef.current.setAngvel(v3toRapier(gameObj.angularVelocity), true);
|
||||
},
|
||||
);
|
||||
},
|
||||
[object.id]
|
||||
);
|
||||
|
||||
|
|
@ -59,7 +60,7 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props
|
|||
<SyncRigidBody
|
||||
ref={rbRef}
|
||||
colliders={false}
|
||||
type={object.physics ? 'dynamic' : 'fixed'}
|
||||
type={props.isPlayer ? 'kinematicPosition' : (object.physics ? 'dynamic' : 'fixed')}
|
||||
gravityScale={object.gravityScale}
|
||||
position={object.position}
|
||||
rotation={object.rotation}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import type { RuntimeScene } from "../types";
|
||||
import { CharacterView } from "./CharacterView";
|
||||
import { GameObjectView } from "./GameObjectView";
|
||||
import { ObjectEditorView } from "./ObjectEditorView";
|
||||
|
||||
|
|
@ -10,30 +9,15 @@ type SceneViewProps = {
|
|||
}
|
||||
|
||||
export const SceneView = observer(function (props: SceneViewProps) {
|
||||
// const rapier = useRapier();
|
||||
|
||||
// useFrame((_, dt) => {
|
||||
// if (props.editMode)
|
||||
// return;
|
||||
|
||||
// const game = state.game;
|
||||
// if (!game || game.isPaused)
|
||||
// return;
|
||||
|
||||
// rapier.step(dt);
|
||||
// })
|
||||
|
||||
return (<>
|
||||
<ambientLight intensity={0.5} />
|
||||
<directionalLight position={[5, 5, 5]} intensity={1} />
|
||||
{
|
||||
Object.values(props.scene.objects).map((obj) => (
|
||||
props.editMode
|
||||
? <ObjectEditorView key={obj.id} object={obj} />
|
||||
: <GameObjectView key={obj.id} object={obj} />
|
||||
? <ObjectEditorView key={obj.id} object={obj} isPlayer={props.scene.playerObjectId === obj.id} />
|
||||
: <GameObjectView key={obj.id} object={obj} isPlayer={props.scene.playerObjectId === obj.id} />
|
||||
))
|
||||
}
|
||||
{/* {props.editMode && <CharacterView character={props.scene.character} />} */}
|
||||
{<CharacterView character={props.scene.character} editMode={props.editMode} />}
|
||||
</>);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@ import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
|||
import { KeyboardControls, Stats } from '@react-three/drei';
|
||||
import { action } from 'mobx';
|
||||
import { chartRef } from './chartRef';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { state } from '../state';
|
||||
import { GameView } from './GameView';
|
||||
import { SceneEditorView } from './SceneEditorView';
|
||||
import { JoystickView } from './JoystickView';
|
||||
import type { RefObject } from 'react';
|
||||
import { IconPlayerPlayFilled, IconPlayerPauseFilled, IconPlayerStopFilled } from '@tabler/icons-react';
|
||||
|
||||
function RenderInfoUpdater() {
|
||||
const { gl } = useThree();
|
||||
|
|
@ -20,19 +27,8 @@ function RenderInfoUpdater() {
|
|||
return null;
|
||||
}
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { state } from '../state';
|
||||
import { GameView } from './GameView';
|
||||
import { SceneEditorView } from './SceneEditorView';
|
||||
import { JoystickView } from './JoystickView';
|
||||
import type { RefObject } from 'react';
|
||||
|
||||
const IconStop = () => <svg viewBox="0 0 14 14"><rect x="2" y="2" width="10" height="10" fill="currentColor" /></svg>;
|
||||
const IconPause = () => <svg viewBox="0 0 14 14"><rect x="2" y="2" width="4" height="10" fill="currentColor" /><rect x="8" y="2" width="4" height="10" fill="currentColor" /></svg >;
|
||||
const IconPlay = () => <svg viewBox="0 0 14 14"><polygon points="3,1 13,7 3,13" fill="currentColor" /></svg>;
|
||||
|
||||
export const ThreeView = observer(function () {
|
||||
const isGame = state.isGamePlaying;
|
||||
const isGame = !!state.game;
|
||||
return (
|
||||
<KeyboardControls map={[
|
||||
{ name: 'forward', keys: ['ArrowUp', 'w', 'W'] },
|
||||
|
|
@ -51,18 +47,18 @@ export const ThreeView = observer(function () {
|
|||
{isGame ? <GameView /> : <SceneEditorView />}
|
||||
</Canvas>
|
||||
{isGame && <JoystickView />}
|
||||
<div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4 }}>
|
||||
<div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4, fontSize: 14 }}>
|
||||
{
|
||||
state.game
|
||||
? <>
|
||||
<button onClick={() => state.stopGame()}><IconStop /></button>
|
||||
<button onClick={() => state.stopGame()}><IconPlayerStopFilled size="1em" /></button>
|
||||
{
|
||||
state.game!.isPaused
|
||||
? <button onClick={() => state.game!.resume()}><IconPlay /></button>
|
||||
: <button onClick={() => state.game!.pause()}><IconPause /></button>
|
||||
? <button onClick={() => state.game!.resume()}><IconPlayerPlayFilled size="1em" /></button>
|
||||
: <button onClick={() => state.game!.pause()}><IconPlayerPauseFilled size="1em" /></button>
|
||||
}
|
||||
</>
|
||||
: <button onClick={() => state.startGame()}><IconPlay /></button>
|
||||
: <button onClick={() => state.startGame()}><IconPlayerPlayFilled size="1em" /></button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -80,11 +80,11 @@ export class GameFactory {
|
|||
.filter((ot) => ot.javascript)
|
||||
.forEach((ot) => {
|
||||
const otCode = eval(`(function gameScript({onGameEvent, offGameEvent }, {api, object, objectType}) {${ot.javascript}})`);
|
||||
const api = new ObjectApi(o, ot, world, scene);
|
||||
|
||||
Object.values(scene.objects)
|
||||
.filter((o) => o.typeId == ot.id)
|
||||
.forEach((o) => {
|
||||
const api = new ObjectApi(o, ot, world, scene);
|
||||
otCode(internalBus, { api, o, ot });
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,12 +9,6 @@ export class WorldFactory {
|
|||
objectTypes: {},
|
||||
voxelTypes: DEFAULT_VOXEL_TYPES,
|
||||
initialScene: {
|
||||
character: {
|
||||
transform: {
|
||||
position: [0, 0, 0],
|
||||
look: [0, 0, 0],
|
||||
},
|
||||
},
|
||||
objects: {},
|
||||
},
|
||||
gameRules: {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { makeAutoObservable, reaction, toJS } from "mobx";
|
||||
import type { WorldState } from "./worldState";
|
||||
import type { Game, Pos3, V3, R3, RuntimeGameScene } from "../types";
|
||||
import type { Game, Pos3, RuntimeGameScene } from "../types";
|
||||
import type { CameraProps } from "@react-three/fiber";
|
||||
import { GameEventBus, GameFactory } from "../model/gameFactory";
|
||||
|
||||
|
|
@ -83,7 +83,6 @@ export class GameState {
|
|||
|
||||
public get camera(): Pos3 {
|
||||
return this.world.data.editorCamera;
|
||||
return this.scene.character.transform;
|
||||
}
|
||||
|
||||
public get cameraAsThree(): CameraProps {
|
||||
|
|
@ -122,23 +121,6 @@ export class GameState {
|
|||
this.isPaused = true;
|
||||
}
|
||||
|
||||
public setCharacterTransform(
|
||||
transform: Pos3,
|
||||
linearVelocity?: V3,
|
||||
angularVelocity?: R3,
|
||||
): void {
|
||||
if (this.isPaused)
|
||||
return;
|
||||
|
||||
this.scene.character.transform = transform;
|
||||
if (linearVelocity)
|
||||
this.scene.character.linearVelocity = linearVelocity;
|
||||
if (angularVelocity)
|
||||
this.scene.character.angularVelocity = angularVelocity;
|
||||
|
||||
// console.log(`changed character to ${JSON.stringify(this.scene.character)}`);
|
||||
}
|
||||
|
||||
public tick(deltaTime: number): void {
|
||||
if (this.isPaused)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
import { makeAutoObservable } from "mobx";
|
||||
import { state } from "./rootState";
|
||||
import type { ReactNode } from "react";
|
||||
import { IconRun } from '@tabler/icons-react';
|
||||
|
||||
export type MenuNodeAction = {
|
||||
id: string;
|
||||
content: ReactNode;
|
||||
tooltip: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export type MenuNode = {
|
||||
id: string;
|
||||
title: string;
|
||||
className?: string;
|
||||
actions?: MenuNodeAction[];
|
||||
onClick?: () => void;
|
||||
selected?: () => boolean;
|
||||
children?: MenuNode[];
|
||||
|
|
@ -26,10 +37,21 @@ export class MenuState {
|
|||
.map((o) => ({
|
||||
id: `o-${o.id}`,
|
||||
title: o.id,
|
||||
className: state.worldEditor.scene.playerObjectId === o.id
|
||||
? 'player-controlled-object'
|
||||
: undefined,
|
||||
actions: [
|
||||
{
|
||||
id: 'control-by-player',
|
||||
content: <IconRun size="1em" />,
|
||||
tooltip: 'Mark as player',
|
||||
onClick: () => { state.markObjectAsPlayer(o); },
|
||||
}
|
||||
],
|
||||
onClick: () => { state.worldEditor.setSelectedObject({ id: o.id, editMode: 'translate' }) },
|
||||
selected: () => state.worldEditor.selection?.type === 'object' && state.worldEditor.selection?.id === o.id,
|
||||
}))
|
||||
}));
|
||||
} as MenuNode))
|
||||
} as MenuNode));
|
||||
}
|
||||
|
||||
private get editorMenu(): MenuNode[] {
|
||||
|
|
@ -3,6 +3,7 @@ import { WorldState } from "./worldState";
|
|||
import { WorldEditorState } from "./worldEditorState";
|
||||
import { GameState } from "./gameState";
|
||||
import { MenuState } from "./menuState";
|
||||
import type { RuntimeObjectInstance } from "../types";
|
||||
|
||||
export type RenderInfo = {
|
||||
calls: number,
|
||||
|
|
@ -31,10 +32,6 @@ export class RootState {
|
|||
);
|
||||
}
|
||||
|
||||
public get isGamePlaying(): boolean {
|
||||
return this.game !== undefined;
|
||||
}
|
||||
|
||||
public startGame(): void {
|
||||
state.worldEditor.resetSelection();
|
||||
if (this.game)
|
||||
|
|
@ -53,6 +50,13 @@ export class RootState {
|
|||
public setRenderInfo(value: RenderInfo | undefined) {
|
||||
this.renderInfo = value;
|
||||
}
|
||||
|
||||
public markObjectAsPlayer(object: RuntimeObjectInstance) {
|
||||
if (this.game)
|
||||
this.game.scene.playerObjectId = object.id;
|
||||
else
|
||||
this.world.data.initialScene.playerObjectId = object.id;
|
||||
}
|
||||
}
|
||||
|
||||
export const state = new RootState();
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export class WorldEditorState {
|
|||
}
|
||||
|
||||
public get isEnabled(): boolean {
|
||||
return !state.isGamePlaying;
|
||||
return !state.game;
|
||||
}
|
||||
|
||||
public setCamera(value: Pos3): void {
|
||||
|
|
|
|||
|
|
@ -72,12 +72,6 @@ export class WorldState {
|
|||
look: [-0.52, -0.35, -0.2],
|
||||
},
|
||||
initialScene: {
|
||||
character: {
|
||||
transform: {
|
||||
position: [0, 5, 20],
|
||||
look: [0, 0, 0],
|
||||
},
|
||||
},
|
||||
objects: {
|
||||
terrain: {
|
||||
id: 'terrain',
|
||||
|
|
@ -90,6 +84,7 @@ export class WorldState {
|
|||
},
|
||||
...objectMap,
|
||||
},
|
||||
playerObjectId: 'obj1',
|
||||
},
|
||||
gameRules: {
|
||||
gravity: true,
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
import type { Pos3 } from "../3d";
|
||||
import type { GameObjectData } from "./object";
|
||||
|
||||
export type Character = {
|
||||
transform: Pos3,
|
||||
}
|
||||
|
||||
export type GameCharacter = Character & GameObjectData;
|
||||
|
|
@ -3,6 +3,5 @@ export * from './scene';
|
|||
export * from './world';
|
||||
export * from './gameRules';
|
||||
export * from './game';
|
||||
export * from './character';
|
||||
export * from './voxel';
|
||||
export * from './runtime';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export type ObjectInstanceRuntimeData = {
|
|||
colliderMesh: [Float32Array, Uint32Array] | null;
|
||||
};
|
||||
pendingActions: {
|
||||
impulse: { direction: V3, amplitude: number };
|
||||
impulse?: { direction: V3, amplitude: number };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,18 @@
|
|||
import type { Character, GameCharacter } from "./character";
|
||||
import type { GameObjectInstance, ObjectInstance, RuntimeGameObjectInstance, RuntimeObjectInstance } from "./object";
|
||||
|
||||
export type Scene = {
|
||||
character: Character;
|
||||
objects: Record<string, ObjectInstance>;
|
||||
playerObjectId: string | undefined;
|
||||
}
|
||||
|
||||
export type RuntimeScene = {
|
||||
character: Character;
|
||||
export type RuntimeScene = Scene & {
|
||||
objects: Record<string, RuntimeObjectInstance>;
|
||||
}
|
||||
|
||||
export type GameScene = {
|
||||
character: GameCharacter;
|
||||
export type GameScene = Scene & {
|
||||
objects: Record<string, GameObjectInstance>;
|
||||
}
|
||||
|
||||
export type RuntimeGameScene = {
|
||||
character: GameCharacter;
|
||||
export type RuntimeGameScene = Scene & {
|
||||
objects: Record<string, RuntimeGameObjectInstance>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ export function populateRuntimeObject(object: ObjectInstance, world: World): Run
|
|||
voxelGroups: getObjectVoxelGroups(objectType, world.voxelTypes),
|
||||
colliderMesh: buildObjectTrimesh(objectType, world.voxelTypes),
|
||||
},
|
||||
pendingActions: {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue