object move blocks
debug floaters
This commit is contained in:
@@ -1 +1,3 @@
|
|||||||
export * from './applyImpulse';
|
export * from './applyImpulse';
|
||||||
|
export * from './moveInDirection';
|
||||||
|
export * from './moveToPosition';
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import * as Blockly from "blockly";
|
||||||
|
import { javascriptGenerator, Order } from "blockly/javascript";
|
||||||
|
|
||||||
|
Blockly.Blocks['move_in_direction_action'] = {
|
||||||
|
init(this: Blockly.Block) {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField('Move me in direction');
|
||||||
|
this.appendValueInput('DIRECTION')
|
||||||
|
.setCheck('DPos');
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField('for')
|
||||||
|
.appendField(new Blockly.FieldNumber(5, 0.01, 10000, 0.1), 'DISTANCE')
|
||||||
|
.appendField('with speed')
|
||||||
|
.appendField(new Blockly.FieldNumber(5, 0.01, 1000, 0.1), 'SPEED');
|
||||||
|
this.setInputsInline(true);
|
||||||
|
this.setPreviousStatement(true, null);
|
||||||
|
this.setNextStatement(true, null);
|
||||||
|
this.setTooltip('Move this object in the given direction for the given distance at the given speed (units/sec), blocking until done');
|
||||||
|
this.setColour(20);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
javascriptGenerator.forBlock['move_in_direction_action'] = function (block, generator) {
|
||||||
|
const direction = generator.valueToCode(block, 'DIRECTION', Order.ATOMIC) || '[0,0,1]';
|
||||||
|
const distance = block.getFieldValue('DISTANCE');
|
||||||
|
const speed = block.getFieldValue('SPEED');
|
||||||
|
return `yield helpers.actions.MoveInDirection(${direction}, ${distance}, ${speed});\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { };
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import * as Blockly from "blockly";
|
||||||
|
import { javascriptGenerator, Order } from "blockly/javascript";
|
||||||
|
|
||||||
|
Blockly.Blocks['move_to_position_action'] = {
|
||||||
|
init(this: Blockly.Block) {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField('Move me to position');
|
||||||
|
this.appendValueInput('TARGET_POSITION')
|
||||||
|
.setCheck('Pos');
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField('with speed')
|
||||||
|
.appendField(new Blockly.FieldNumber(5, 0.01, 1000, 0.1), 'SPEED');
|
||||||
|
this.setInputsInline(true);
|
||||||
|
this.setPreviousStatement(true, null);
|
||||||
|
this.setNextStatement(true, null);
|
||||||
|
this.setTooltip('Move this object to the target position at the given speed (units/sec), blocking until arrived');
|
||||||
|
this.setColour(20);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
javascriptGenerator.forBlock['move_to_position_action'] = function (block, generator) {
|
||||||
|
const targetPosition = generator.valueToCode(block, 'TARGET_POSITION', Order.ATOMIC) || '[0,0,0]';
|
||||||
|
const speed = block.getFieldValue('SPEED');
|
||||||
|
return `yield helpers.actions.MoveToPosition(${targetPosition}, ${speed});\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { };
|
||||||
@@ -2,6 +2,7 @@ import * as Blockly from "blockly";
|
|||||||
import { javascriptGenerator } from "blockly/javascript";
|
import { javascriptGenerator } from "blockly/javascript";
|
||||||
|
|
||||||
export function generateCode(workspace: Blockly.WorkspaceSvg): string {
|
export function generateCode(workspace: Blockly.WorkspaceSvg): string {
|
||||||
|
javascriptGenerator.init(workspace);
|
||||||
let result = '';
|
let result = '';
|
||||||
for (const block of workspace.getTopBlocks(true)) {
|
for (const block of workspace.getTopBlocks(true)) {
|
||||||
if (block.type.endsWith('_event')) {
|
if (block.type.endsWith('_event')) {
|
||||||
@@ -21,9 +22,11 @@ export function generateCode(workspace: Blockly.WorkspaceSvg): string {
|
|||||||
|
|
||||||
//TODO use args
|
//TODO use args
|
||||||
|
|
||||||
|
const eventName = block.type.replace(/_event$/, '');
|
||||||
|
|
||||||
result += `eventBus.on(
|
result += `eventBus.on(
|
||||||
'${block.type.replace(/_event$/, '')}',
|
'${eventName}',
|
||||||
function*(context) {
|
function* handle_${eventName}(context) {
|
||||||
if (context.object && (context.object.id !== thisObject.id))
|
if (context.object && (context.object.id !== thisObject.id))
|
||||||
return;
|
return;
|
||||||
${body} },
|
${body} },
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export * from './actions';
|
|||||||
export * from './theme';
|
export * from './theme';
|
||||||
export * from './generateCode';
|
export * from './generateCode';
|
||||||
export * from './fieldTypes';
|
export * from './fieldTypes';
|
||||||
|
export * from './loops';
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './repeatForever';
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import * as Blockly from "blockly";
|
||||||
|
import { javascriptGenerator } from "blockly/javascript";
|
||||||
|
|
||||||
|
Blockly.Blocks['loop_forever'] = {
|
||||||
|
init(this: Blockly.Block) {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField('Repeat forever');
|
||||||
|
this.appendStatementInput('BODY')
|
||||||
|
.setCheck(null);
|
||||||
|
this.setPreviousStatement(true, null);
|
||||||
|
this.setNextStatement(true, null);
|
||||||
|
this.setTooltip('Repeat the enclosed actions forever');
|
||||||
|
this.setColour(120);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
javascriptGenerator.forBlock['loop_forever'] = function (block, generator) {
|
||||||
|
const body = generator.statementToCode(block, 'BODY');
|
||||||
|
return `while (true) {\n${body}${generator.INDENT}yield helpers.actions.WaitFrames(1);\n}\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { };
|
||||||
@@ -16,7 +16,7 @@ Blockly.Blocks['direction_to_position_value'] = {
|
|||||||
|
|
||||||
javascriptGenerator.forBlock['direction_to_position_value'] = function (block, generator) {
|
javascriptGenerator.forBlock['direction_to_position_value'] = function (block, generator) {
|
||||||
const targetValue = generator.valueToCode(block, 'TARGET', Order.ATOMIC);
|
const targetValue = generator.valueToCode(block, 'TARGET', Order.ATOMIC);
|
||||||
return [`((a,b)=>[a[0]-b[0],a[1]-b[1],a[2]-b[2]])(${targetValue},object.position)`, Order.NONE];
|
return [`(t=>{const p=helpers.positionOfObject(thisObject); const dir = [t[0]-p[0],t[1]-p[1],t[2]-p[2]]; /* helpers.debug.show('${block.id}.targetPosition', ${targetValue}); helpers.debug.show('${block.id}.result', dir); helpers.debug.show('${block.id}.thisObject.position', p); */ return dir;})(${targetValue})`, Order.NONE];
|
||||||
};
|
};
|
||||||
|
|
||||||
export { };
|
export { };
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Blockly.Blocks['position_of_object_value'] = {
|
|||||||
|
|
||||||
javascriptGenerator.forBlock['position_of_object_value'] = function (block, generator) {
|
javascriptGenerator.forBlock['position_of_object_value'] = function (block, generator) {
|
||||||
const targetValue = generator.valueToCode(block, 'TARGET', Order.ATOMIC);
|
const targetValue = generator.valueToCode(block, 'TARGET', Order.ATOMIC);
|
||||||
return [`${targetValue}.position`, Order.NONE];
|
return [`helpers.positionOfObject(${targetValue})`, Order.NONE];
|
||||||
};
|
};
|
||||||
|
|
||||||
export { };
|
export { };
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const PlayerObjectView = observer(function ({ object, objectType }: Playe
|
|||||||
};
|
};
|
||||||
}, [object.cache.boundingBox]);
|
}, [object.cache.boundingBox]);
|
||||||
|
|
||||||
const yawRef = useRef(0);
|
const yawRef = useRef(object.rotation[1]);
|
||||||
const pitchRef = useRef(0);
|
const pitchRef = useRef(0);
|
||||||
const mouseRef = useRef({ x: 0, y: 0 });
|
const mouseRef = useRef({ x: 0, y: 0 });
|
||||||
const jumpPressedRef = useRef(false);
|
const jumpPressedRef = useRef(false);
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Html } from "@react-three/drei";
|
||||||
|
import { useFrame } from "@react-three/fiber";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import type { RuntimeObjectInstance } from "../types";
|
||||||
|
import { debugLabelsStore } from "../state/debugLabelsStore";
|
||||||
|
import { state } from "../state";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
export const DebugLabel = observer(function ({ obj }: { obj: RuntimeObjectInstance; }) {
|
||||||
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
|
|
||||||
|
const rb = obj.rb();
|
||||||
|
const texts = [
|
||||||
|
`${state.world.getObjectTypeById(obj.typeId)?.name} ${obj.id}`,
|
||||||
|
`position: ${obj.position}`,
|
||||||
|
`rotation: ${obj.rotation}`,
|
||||||
|
...Object.entries(debugLabelsStore.getAll(obj.id))
|
||||||
|
.map(([k, v]) => `${k}: ${v}`),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (rb) {
|
||||||
|
try {
|
||||||
|
const t = rb.translation();
|
||||||
|
texts.push(...[
|
||||||
|
`rb position: ${[t.x, t.y, t.z]}`,
|
||||||
|
]);
|
||||||
|
} catch {
|
||||||
|
texts.push('rb: stale handle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (!groupRef.current)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const [px, py, pz] = obj.position;
|
||||||
|
// const bb = obj.cache.boundingBox;
|
||||||
|
// const halfHeight = bb ? (bb.max[1] - bb.min[1]) / 2 : 1;
|
||||||
|
// groupRef.current.position.set(px, py + halfHeight + 0.5, pz);
|
||||||
|
groupRef.current.position.set(px, py, pz);
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = texts.join('\n');
|
||||||
|
if (!text)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={groupRef}>
|
||||||
|
<Html center sprite>
|
||||||
|
<div style={{
|
||||||
|
background: 'rgba(0,0,0,0.65)',
|
||||||
|
color: '#fff',
|
||||||
|
padding: '2px 7px',
|
||||||
|
borderRadius: 4,
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
whiteSpace: 'pre',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
userSelect: 'none',
|
||||||
|
}}>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
</Html>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { debugLabelsStore } from "../state/debugLabelsStore";
|
||||||
|
import { DebugLabel } from "./DebugLabel";
|
||||||
|
import type { RuntimeScene } from "../types";
|
||||||
|
|
||||||
|
export const DebugLabelsView = observer(function ({ scene }: { scene: RuntimeScene }) {
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
return () => { debugLabelsStore.clear(); };
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{
|
||||||
|
Object.entries(scene.objects)
|
||||||
|
.map(([objectId, obj]) => {
|
||||||
|
return <DebugLabel key={objectId} obj={obj} />;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</>;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { SceneView } from "./SceneView";
|
import { SceneView } from "./SceneView";
|
||||||
|
import { DebugLabelsView } from "./DebugLabelsView";
|
||||||
import { state } from "../state";
|
import { state } from "../state";
|
||||||
import { useFrame } from "@react-three/fiber";
|
import { useFrame } from "@react-three/fiber";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
import type { ObjectInstance, R3, Runtime } from "../types";
|
import type { ObjectInstance, R3, Runtime } from "../types";
|
||||||
import { useMemo, useRef, type RefObject } from "react";
|
import { useMemo, useRef, type RefObject } from "react";
|
||||||
import type { Group } from "three";
|
import type { Group } from "three";
|
||||||
|
import { Euler, Quaternion, Vector3 } from "three";
|
||||||
import { TransformControls, useHelper } from "@react-three/drei";
|
import { TransformControls, useHelper } from "@react-three/drei";
|
||||||
import { BoxHelper } from "three";
|
import { BoxHelper } from "three";
|
||||||
import { state } from "../state";
|
import { state } from "../state";
|
||||||
@@ -16,12 +18,13 @@ type ObjectEditorViewProps = {
|
|||||||
type SelectionOverlayProps = {
|
type SelectionOverlayProps = {
|
||||||
objectId: string;
|
objectId: string;
|
||||||
ref: RefObject<ObjectViewInternalHandle | null>;
|
ref: RefObject<ObjectViewInternalHandle | null>;
|
||||||
|
onTransformChange: () => void;
|
||||||
onTransformEnd: () => void;
|
onTransformEnd: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separate observer so only the 2 affected instances (selected/deselected)
|
// Separate observer so only the 2 affected instances (selected/deselected)
|
||||||
// re-render on selection change, not all N objects in the scene.
|
// re-render on selection change, not all N objects in the scene.
|
||||||
const SelectionOverlay = observer(function ({ objectId, ref, onTransformEnd }: SelectionOverlayProps) {
|
const SelectionOverlay = observer(function ({ objectId, ref, onTransformChange, onTransformEnd }: SelectionOverlayProps) {
|
||||||
const isSelected = state.worldEditor.isEnabled &&
|
const isSelected = state.worldEditor.isEnabled &&
|
||||||
state.worldEditor.selection?.type === 'object' &&
|
state.worldEditor.selection?.type === 'object' &&
|
||||||
state.worldEditor.selection.id === objectId;
|
state.worldEditor.selection.id === objectId;
|
||||||
@@ -45,6 +48,7 @@ const SelectionOverlay = observer(function ({ objectId, ref, onTransformEnd }: S
|
|||||||
<TransformControls
|
<TransformControls
|
||||||
object={groupRef as RefObject<Group>}
|
object={groupRef as RefObject<Group>}
|
||||||
mode={selectionMode}
|
mode={selectionMode}
|
||||||
|
onChange={onTransformChange}
|
||||||
onMouseUp={onTransformEnd}
|
onMouseUp={onTransformEnd}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -72,14 +76,30 @@ export const ObjectEditorView = observer(function ({ object, isPlayer }: ObjectE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleTransformChange() {
|
||||||
|
const group = dataRef.current?.group;
|
||||||
|
if (!group) return;
|
||||||
|
const pos = group.getWorldPosition(new Vector3());
|
||||||
|
const quat = group.getWorldQuaternion(new Quaternion());
|
||||||
|
const euler = new Euler().setFromQuaternion(quat);
|
||||||
|
runInAction(() => {
|
||||||
|
object.position = pos.toArray() as R3;
|
||||||
|
object.rotation = [euler.x, euler.y, euler.z];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function handleTransformEnd() {
|
function handleTransformEnd() {
|
||||||
const group = dataRef.current?.group;
|
const group = dataRef.current?.group;
|
||||||
if (group)
|
if (!group) return;
|
||||||
|
const pos = group.getWorldPosition(new Vector3());
|
||||||
|
const quat = group.getWorldQuaternion(new Quaternion());
|
||||||
|
const euler = new Euler().setFromQuaternion(quat);
|
||||||
|
const scale = group.getWorldScale(new Vector3());
|
||||||
state.worldEditor.setObjectTransform(
|
state.worldEditor.setObjectTransform(
|
||||||
object.id,
|
object.id,
|
||||||
group.position.toArray(),
|
pos.toArray(),
|
||||||
group.rotation.toArray().slice(0, 3) as R3,
|
[euler.x, euler.y, euler.z],
|
||||||
group.scale.toArray(),
|
scale.toArray(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +107,7 @@ export const ObjectEditorView = observer(function ({ object, isPlayer }: ObjectE
|
|||||||
<SelectionOverlay
|
<SelectionOverlay
|
||||||
objectId={object.id}
|
objectId={object.id}
|
||||||
ref={dataRef}
|
ref={dataRef}
|
||||||
|
onTransformChange={handleTransformChange}
|
||||||
onTransformEnd={handleTransformEnd}
|
onTransformEnd={handleTransformEnd}
|
||||||
/>
|
/>
|
||||||
<ObjectViewInternal
|
<ObjectViewInternal
|
||||||
|
|||||||
@@ -2,9 +2,13 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { LeftPanel } from "./LeftPanel";
|
import { LeftPanel } from "./LeftPanel";
|
||||||
import { MainPanel } from "./MainPanel";
|
import { MainPanel } from "./MainPanel";
|
||||||
import './Panels.scss';
|
import './Panels.scss';
|
||||||
|
import { state } from "../state";
|
||||||
|
|
||||||
export const Panels = observer(function () {
|
export const Panels = observer(function () {
|
||||||
|
|
||||||
|
if (state.game)
|
||||||
|
return null;
|
||||||
|
|
||||||
return <div className="overlay-panels">
|
return <div className="overlay-panels">
|
||||||
<LeftPanel />
|
<LeftPanel />
|
||||||
<MainPanel />
|
<MainPanel />
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import type { RuntimeScene } from "../types";
|
import type { RuntimeGameObjectInstance, RuntimeScene } from "../types";
|
||||||
import { GameObjectView } from "./GameObjectView";
|
import { GameObjectView } from "./GameObjectView";
|
||||||
import { ObjectEditorView } from "./ObjectEditorView";
|
import { ObjectEditorView } from "./ObjectEditorView";
|
||||||
|
import { DebugLabelsView } from "./DebugLabelsView";
|
||||||
|
|
||||||
type SceneViewProps = {
|
type SceneViewProps = {
|
||||||
scene: RuntimeScene,
|
scene: RuntimeScene,
|
||||||
@@ -14,10 +15,11 @@ export const SceneView = observer(function (props: SceneViewProps) {
|
|||||||
<directionalLight position={[5, 5, 5]} intensity={1} />
|
<directionalLight position={[5, 5, 5]} intensity={1} />
|
||||||
{
|
{
|
||||||
Object.values(props.scene.objects).map((obj) => (
|
Object.values(props.scene.objects).map((obj) => (
|
||||||
props.editMode
|
!props.editMode
|
||||||
? <ObjectEditorView key={obj.id} object={obj} isPlayer={props.scene.playerObjectId === obj.id} />
|
? <GameObjectView key={obj.id} object={obj as RuntimeGameObjectInstance} isPlayer={props.scene.playerObjectId === obj.id} />
|
||||||
: <GameObjectView key={obj.id} object={obj} isPlayer={props.scene.playerObjectId === obj.id} />
|
: <ObjectEditorView key={obj.id} object={obj} isPlayer={props.scene.playerObjectId === obj.id} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
<DebugLabelsView scene={props.scene} />
|
||||||
</>);
|
</>);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { GameView } from './GameView';
|
|||||||
import { SceneEditorView } from './SceneEditorView';
|
import { SceneEditorView } from './SceneEditorView';
|
||||||
import { JoystickView } from './JoystickView';
|
import { JoystickView } from './JoystickView';
|
||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
import { IconPlayerPlayFilled, IconPlayerPauseFilled, IconPlayerStopFilled } from '@tabler/icons-react';
|
import { IconPlayerPlayFilled, IconPlayerPauseFilled, IconPlayerStopFilled, IconPlayerSkipForwardFilled } from '@tabler/icons-react';
|
||||||
import { RenderInfoUpdater } from './renderInfo';
|
import { RenderInfoUpdater } from './renderInfo';
|
||||||
|
|
||||||
export const ThreeView = observer(function () {
|
export const ThreeView = observer(function () {
|
||||||
@@ -37,7 +37,10 @@ export const ThreeView = observer(function () {
|
|||||||
<button onClick={() => state.stopGame()}><IconPlayerStopFilled size="1em" /></button>
|
<button onClick={() => state.stopGame()}><IconPlayerStopFilled size="1em" /></button>
|
||||||
{
|
{
|
||||||
state.game!.isPaused
|
state.game!.isPaused
|
||||||
? <button onClick={() => state.game!.resume()}><IconPlayerPlayFilled size="1em" /></button>
|
? <>
|
||||||
|
<button onClick={() => state.game!.resume()}><IconPlayerPlayFilled size="1em" /></button>
|
||||||
|
<button onClick={() => state.game!.step()}><IconPlayerSkipForwardFilled size="1em" /></button>
|
||||||
|
</>
|
||||||
: <button onClick={() => state.game!.pause()}><IconPlayerPauseFilled size="1em" /></button>
|
: <button onClick={() => state.game!.pause()}><IconPlayerPauseFilled size="1em" /></button>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,8 +4,80 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
.script-editor-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
background: #2a2a2a;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 2px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.blockly-container {
|
.blockly-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.script-editor-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.script-editor-popup {
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 70vw;
|
||||||
|
max-height: 75vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.script-editor-popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #2d2d2d;
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.script-editor-popup-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #aaa;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1;
|
||||||
|
&:hover { color: #fff; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.script-editor-popup-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { runInAction } from "mobx";
|
import { runInAction } from "mobx";
|
||||||
import * as Blockly from "blockly";
|
import * as Blockly from "blockly";
|
||||||
@@ -39,12 +39,22 @@ const TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = {
|
|||||||
{ kind: 'block', type: 'player_object_value' },
|
{ kind: 'block', type: 'player_object_value' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
kind: 'category',
|
||||||
|
name: 'Loops',
|
||||||
|
colour: '50',
|
||||||
|
contents: [
|
||||||
|
{ kind: 'block', type: 'loop_forever' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
kind: 'category',
|
kind: 'category',
|
||||||
name: 'Actions',
|
name: 'Actions',
|
||||||
colour: '20',
|
colour: '20',
|
||||||
contents: [
|
contents: [
|
||||||
{ kind: 'block', type: 'console_log_action' },
|
{ kind: 'block', type: 'console_log_action' },
|
||||||
|
{ kind: 'block', type: 'move_to_position_action' },
|
||||||
|
{ kind: 'block', type: 'move_in_direction_action' },
|
||||||
{ kind: 'block', type: 'physics_apply_impulse_action' },
|
{ kind: 'block', type: 'physics_apply_impulse_action' },
|
||||||
{ kind: 'block', type: 'wait_seconds_action' },
|
{ kind: 'block', type: 'wait_seconds_action' },
|
||||||
],
|
],
|
||||||
@@ -65,6 +75,7 @@ type ScriptEditorViewProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorViewProps) {
|
export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorViewProps) {
|
||||||
|
const [popup, setPopup] = useState<{ title: string; content: string } | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const workspaceRef = useRef<Blockly.WorkspaceSvg | null>(null);
|
const workspaceRef = useRef<Blockly.WorkspaceSvg | null>(null);
|
||||||
const objectTypeRef = useRef(objectType);
|
const objectTypeRef = useRef(objectType);
|
||||||
@@ -86,8 +97,6 @@ export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorV
|
|||||||
const serialized = JSON.stringify(Blockly.serialization.workspaces.save(workspace));
|
const serialized = JSON.stringify(Blockly.serialization.workspaces.save(workspace));
|
||||||
current.program = serialized;
|
current.program = serialized;
|
||||||
|
|
||||||
console.log(serialized);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
current.javascript = generateCode(workspace);
|
current.javascript = generateCode(workspace);
|
||||||
}
|
}
|
||||||
@@ -95,7 +104,6 @@ export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorV
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
current.javascript = undefined;
|
current.javascript = undefined;
|
||||||
}
|
}
|
||||||
console.log(current.javascript);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
workspace.addChangeListener(listener);
|
workspace.addChangeListener(listener);
|
||||||
@@ -139,7 +147,34 @@ export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorV
|
|||||||
}
|
}
|
||||||
}, [objectType.id, objectType.program]);
|
}, [objectType.id, objectType.program]);
|
||||||
|
|
||||||
|
const showProgram = () => {
|
||||||
|
const raw = objectType.program;
|
||||||
|
setPopup({ title: 'Program JSON', content: raw ? JSON.stringify(JSON.parse(raw), null, 2) : '(empty)' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const showJavascript = () => {
|
||||||
|
setPopup({ title: 'JavaScript', content: objectType.javascript ?? '(empty)' });
|
||||||
|
};
|
||||||
|
|
||||||
return <div className="script-editor">
|
return <div className="script-editor">
|
||||||
|
<div className="script-editor-toolbar">
|
||||||
|
<button onClick={showProgram}>Program JSON</button>
|
||||||
|
<button onClick={showJavascript}>JavaScript</button>
|
||||||
|
</div>
|
||||||
<div ref={containerRef} className="blockly-container" />
|
<div ref={containerRef} className="blockly-container" />
|
||||||
|
{popup && (
|
||||||
|
<div className="script-editor-overlay" onClick={() => setPopup(null)}>
|
||||||
|
<div className="script-editor-popup" onClick={e => e.stopPropagation()}>
|
||||||
|
<div className="script-editor-popup-header">
|
||||||
|
<span>{popup.title}</span>
|
||||||
|
<div className="script-editor-popup-actions">
|
||||||
|
<button onClick={() => navigator.clipboard.writeText(popup.content)}>Copy all</button>
|
||||||
|
<button onClick={() => setPopup(null)}>✕</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<pre className="script-editor-popup-content">{popup.content}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>;
|
</div>;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { clone } from "../utils";
|
|||||||
import { populateRuntimeScene } from "../utils/runtime";
|
import { populateRuntimeScene } from "../utils/runtime";
|
||||||
import { ScriptGameEventBus } from "../scripts/eventBus";
|
import { ScriptGameEventBus } from "../scripts/eventBus";
|
||||||
import * as ScriptActions from "../scripts/actions";
|
import * as ScriptActions from "../scripts/actions";
|
||||||
|
import { positionOfObject } from "../scripts/positionOfObject";
|
||||||
|
import { debugLabelsStore } from "../state/debugLabelsStore";
|
||||||
import type { ScriptCoroutines } from "../scripts/coroutines";
|
import type { ScriptCoroutines } from "../scripts/coroutines";
|
||||||
|
|
||||||
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -51,6 +53,7 @@ export class GameFactory {
|
|||||||
const eventBus = new ScriptGameEventBus(coroutines);
|
const eventBus = new ScriptGameEventBus(coroutines);
|
||||||
|
|
||||||
const scriptHelpers = {
|
const scriptHelpers = {
|
||||||
|
positionOfObject,
|
||||||
// convert all ScriptActions imported to a structure factory(coroutine):
|
// convert all ScriptActions imported to a structure factory(coroutine):
|
||||||
// {
|
// {
|
||||||
// WaitSeconds: (seconds) => new WaitSecondsScriptAction(coroutine, seconds),
|
// WaitSeconds: (seconds) => new WaitSecondsScriptAction(coroutine, seconds),
|
||||||
@@ -71,7 +74,7 @@ export class GameFactory {
|
|||||||
.filter((ot) => ot.javascript)
|
.filter((ot) => ot.javascript)
|
||||||
.forEach((ot) => {
|
.forEach((ot) => {
|
||||||
try {
|
try {
|
||||||
const javascript = `(function gameScript(eventBus, helpers, thisObject, thisObjectType) {${ot.javascript}})`;
|
const javascript = `(function gameScript(eventBus, helpers, thisObject, thisObjectType) {${ot.javascript}})\n//# sourceURL=GameScript:${ot.name}.js`;
|
||||||
const gameScript = eval(javascript);
|
const gameScript = eval(javascript);
|
||||||
|
|
||||||
Object.values(scene.objects)
|
Object.values(scene.objects)
|
||||||
@@ -86,16 +89,20 @@ export class GameFactory {
|
|||||||
const localScriptHelpers = {
|
const localScriptHelpers = {
|
||||||
...scriptHelpers,
|
...scriptHelpers,
|
||||||
actions: scriptHelpers.actions({ thisObject: o, thisObjectType: ot }),
|
actions: scriptHelpers.actions({ thisObject: o, thisObjectType: ot }),
|
||||||
|
debug: {
|
||||||
|
show: (key: string, text: string) => debugLabelsStore.setOne(o.id, key, text),
|
||||||
|
clear: (key: string) => debugLabelsStore.deleteOne(o.id, key),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
gameScript(eventBus, localScriptHelpers, o, ot);
|
gameScript(eventBus, localScriptHelpers, o, ot);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.log('Error running object instance script:\n' + err);
|
console.error(`[GameScript:${ot.name}] Error running script for object ${o.id}:`, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.log('Error evaluating object type script:\n' + err);
|
console.error(`[GameScript:${ot.name}] Error evaluating script:`, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,5 @@ export * from './waitConditionAction';
|
|||||||
export * from './waitFramesAction';
|
export * from './waitFramesAction';
|
||||||
export * from './waitSecondsAction';
|
export * from './waitSecondsAction';
|
||||||
export * from './applyImpulseAction';
|
export * from './applyImpulseAction';
|
||||||
|
export * from './moveToPositionAction';
|
||||||
|
export * from './moveInDirectionAction';
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { ScriptAction, type ScriptCoroutineContext, type V3 } from "../../types";
|
||||||
|
|
||||||
|
const EPS = 0.05;
|
||||||
|
|
||||||
|
export class MoveInDirectionScriptAction extends ScriptAction {
|
||||||
|
|
||||||
|
private direction: V3;
|
||||||
|
private distance: number;
|
||||||
|
private speed: number;
|
||||||
|
private traveled: number = 0;
|
||||||
|
|
||||||
|
constructor(coroutine: ScriptCoroutineContext, direction: V3, distance: number, speed: number) {
|
||||||
|
super(coroutine);
|
||||||
|
const len = Math.sqrt(direction[0] ** 2 + direction[1] ** 2 + direction[2] ** 2);
|
||||||
|
this.direction = len > 1e-6
|
||||||
|
? [direction[0] / len, direction[1] / len, direction[2] / len]
|
||||||
|
: [0, 0, 0];
|
||||||
|
this.distance = distance;
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public tick(deltaTime: number): boolean {
|
||||||
|
const rb = this.coroutine.thisObject.rb();
|
||||||
|
if (!rb)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const remaining = this.distance - this.traveled;
|
||||||
|
if (remaining < EPS)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const step = Math.min(this.speed * deltaTime, remaining);
|
||||||
|
const { x, y, z } = rb.translation();
|
||||||
|
|
||||||
|
rb.setTranslation({
|
||||||
|
x: x + this.direction[0] * step,
|
||||||
|
y: y + this.direction[1] * step,
|
||||||
|
z: z + this.direction[2] * step,
|
||||||
|
}, true);
|
||||||
|
rb.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
|
||||||
|
this.traveled += step;
|
||||||
|
return this.traveled >= this.distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { ScriptAction, type ScriptCoroutineContext, type V3 } from "../../types";
|
||||||
|
|
||||||
|
export class MoveToPositionScriptAction extends ScriptAction {
|
||||||
|
|
||||||
|
private targetPosition: V3;
|
||||||
|
private speed: number;
|
||||||
|
|
||||||
|
constructor(coroutine: ScriptCoroutineContext, targetPosition: V3, speed: number) {
|
||||||
|
super(coroutine);
|
||||||
|
this.targetPosition = targetPosition;
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public tick(deltaTime: number): boolean {
|
||||||
|
const rb = this.coroutine.thisObject.rb();
|
||||||
|
if (!rb)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const { x: px, y: py, z: pz } = rb.translation();
|
||||||
|
const dx = this.targetPosition[0] - px;
|
||||||
|
const dy = this.targetPosition[1] - py;
|
||||||
|
const dz = this.targetPosition[2] - pz;
|
||||||
|
const distSq = dx * dx + dy * dy + dz * dz;
|
||||||
|
|
||||||
|
if (distSq < 0.0001) {
|
||||||
|
rb.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dist = Math.sqrt(distSq);
|
||||||
|
const step = this.speed * deltaTime;
|
||||||
|
|
||||||
|
if (step >= dist) {
|
||||||
|
rb.setTranslation({ x: this.targetPosition[0], y: this.targetPosition[1], z: this.targetPosition[2] }, true);
|
||||||
|
rb.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scale = step / dist;
|
||||||
|
rb.setTranslation({ x: px + dx * scale, y: py + dy * scale, z: pz + dz * scale }, true);
|
||||||
|
rb.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +1,32 @@
|
|||||||
import type { ScriptAction, ScriptCoroutine } from "../types";
|
import type { NamedScriptCoroutine, ScriptAction } from "../types";
|
||||||
|
|
||||||
|
type CoroutineItem = NamedScriptCoroutine & {
|
||||||
|
currentAction?: ScriptAction;
|
||||||
|
};
|
||||||
|
|
||||||
export class ScriptCoroutines {
|
export class ScriptCoroutines {
|
||||||
private items: { coroutine: ScriptCoroutine, currentAction?: ScriptAction }[] = [];
|
private items: CoroutineItem[] = [];
|
||||||
|
|
||||||
public push(coroutines: ScriptCoroutine[]) {
|
public push(coroutines: NamedScriptCoroutine[]) {
|
||||||
this.items.push(
|
this.items.push(...coroutines.map((c) => ({ ...c })));
|
||||||
...coroutines
|
|
||||||
.map((c) => ({ coroutine: c })),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public tick(deltaTime: number): void {
|
public tick(deltaTime: number): void {
|
||||||
// execute coroutines one by one and remove the ones that have finished
|
this.items = this.items.filter((c) => !this.tickItem(c, deltaTime));
|
||||||
this.items = this.items
|
|
||||||
.filter((c) => !this.tickItem(c, deltaTime));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private tickItem(item: { coroutine: ScriptCoroutine, currentAction?: ScriptAction }, deltaTime: number): boolean {
|
private tickItem(item: CoroutineItem, deltaTime: number): boolean {
|
||||||
|
|
||||||
if (!item.currentAction) {
|
if (!item.currentAction) {
|
||||||
try {
|
try {
|
||||||
const yieldResult = item.coroutine.next();
|
const yieldResult = item.coroutine.next();
|
||||||
if (yieldResult.done)
|
if (yieldResult.done)
|
||||||
return true; // coroutine exhausted
|
return true;
|
||||||
item.currentAction = yieldResult.value;
|
item.currentAction = yieldResult.value;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(err);
|
console.error(`[${item.name}] Script error:`, err);
|
||||||
return true; // coroutine failed to yield an action or return
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ export class ScriptCoroutines {
|
|||||||
item.currentAction = undefined;
|
item.currentAction = undefined;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(err);
|
console.error(`[${item.name}] Action error in ${item.currentAction?.constructor.name}:`, err);
|
||||||
item.currentAction = undefined;
|
item.currentAction = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,15 +36,12 @@ export class ScriptGameEventBus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emit(event: string, context: Omit<ScriptContext, keyof ScriptCoroutineContext>): void {
|
emit(event: string, context: Omit<ScriptContext, keyof ScriptCoroutineContext>): void {
|
||||||
console.log('emitting ' + event);
|
|
||||||
|
|
||||||
const coroutines = this.handlers[event]
|
const coroutines = this.handlers[event]
|
||||||
?.map((h) => {
|
?.map((h) => ({
|
||||||
console.log(`passing ${event} to ${h.handler}(${JSON.stringify(context)}, ${JSON.stringify(h.coroutine)})`);
|
owner: h.coroutine.thisObject.id,
|
||||||
return h.handler({ ...context, ...h.coroutine });
|
name: `${h.coroutine.thisObjectType.name}[${h.coroutine.thisObject.id}]:${event}`,
|
||||||
});
|
coroutine: h.handler({ ...context, ...h.coroutine }),
|
||||||
|
}));
|
||||||
console.log(coroutines);
|
|
||||||
|
|
||||||
if (coroutines)
|
if (coroutines)
|
||||||
this.coroutines.push(coroutines);
|
this.coroutines.push(coroutines);
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import type { RuntimeGameObjectInstance, V3 } from "../types";
|
||||||
|
|
||||||
|
export function positionOfObject(obj: RuntimeGameObjectInstance): V3 {
|
||||||
|
const t = obj.rb()?.translation();
|
||||||
|
const result: V3 = t
|
||||||
|
? [t.x, t.y, t.z]
|
||||||
|
: [obj.position[0], obj.position[1], obj.position[2]];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { makeAutoObservable } from "mobx";
|
||||||
|
|
||||||
|
class DebugLabelsStore {
|
||||||
|
public labels = new Map<string, Record<string, string>>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAll(objectId: string): Record<string, string> {
|
||||||
|
return this.labels.get(objectId) ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOne(objectId: string, key: string): string | undefined {
|
||||||
|
return this.getAll(objectId)[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
public setOne(objectId: string, key: string, text: string) {
|
||||||
|
if (!this.labels.has(objectId))
|
||||||
|
this.labels.set(objectId, {});
|
||||||
|
this.labels.get(objectId)![key] = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteAll(objectId: string) {
|
||||||
|
this.labels.delete(objectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteOne(objectId: string, key: string) {
|
||||||
|
if (this.labels.has(objectId)) {
|
||||||
|
delete (this.labels.get(objectId)![key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
this.labels.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const debugLabelsStore = new DebugLabelsStore();
|
||||||
@@ -124,6 +124,10 @@ export class GameState {
|
|||||||
if (this.isPaused)
|
if (this.isPaused)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
this.step(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public step(deltaTime: number = 1 / 60): void {
|
||||||
this.coroutines.tick(deltaTime);
|
this.coroutines.tick(deltaTime);
|
||||||
|
|
||||||
this.time += deltaTime;
|
this.time += deltaTime;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import type { ObjectInstance } from "./object";
|
|||||||
|
|
||||||
export type RigidBodyHandle = {
|
export type RigidBodyHandle = {
|
||||||
applyImpulse(impulse: { x: number; y: number; z: number }, wakeUp: boolean): void;
|
applyImpulse(impulse: { x: number; y: number; z: number }, wakeUp: boolean): void;
|
||||||
|
setLinvel(vel: { x: number; y: number; z: number }, wakeUp: boolean): void;
|
||||||
|
setTranslation(translation: { x: number; y: number; z: number }, wakeUp: boolean): void;
|
||||||
translation(): { x: number; y: number; z: number };
|
translation(): { x: number; y: number; z: number };
|
||||||
linvel(): { x: number; y: number; z: number };
|
linvel(): { x: number; y: number; z: number };
|
||||||
angvel(): { x: number; y: number; z: number };
|
angvel(): { x: number; y: number; z: number };
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import type { ObjectType, RuntimeGameObjectInstance, Scene, World } from "../model";
|
import type { ObjectInstance, ObjectType, RuntimeGameObjectInstance, Scene, World } from "../model";
|
||||||
import type { ScriptAction } from "./actions";
|
import type { ScriptAction } from "./actions";
|
||||||
|
|
||||||
export type ScriptCoroutine = Generator<ScriptAction, void, void>;
|
export type ScriptCoroutine = Generator<ScriptAction, void, void>;
|
||||||
|
export type NamedScriptCoroutine = {
|
||||||
|
name: string;
|
||||||
|
owner: ObjectInstance['id'];
|
||||||
|
coroutine: ScriptCoroutine;
|
||||||
|
}
|
||||||
|
|
||||||
export type ScriptCoroutineContext = {
|
export type ScriptCoroutineContext = {
|
||||||
thisObject: RuntimeGameObjectInstance,
|
thisObject: RuntimeGameObjectInstance,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getObjectVoxelGroups } from "../graphics/voxelGroup";
|
import { getObjectVoxelGroups } from "../graphics/voxelGroup";
|
||||||
import type { ObjectInstance, ObjectType, RuntimeObjectInstance, V3, World } from "../../types";
|
import type { GameObjectInstance, ObjectInstance, ObjectType, RuntimeGameObjectInstance, RuntimeObjectInstance, V3, World } from "../../types";
|
||||||
import { buildObjectTrimesh } from "../graphics/mesh";
|
import { buildObjectTrimesh } from "../graphics/mesh";
|
||||||
|
|
||||||
export function computeBoundingBox(objectType: ObjectType): { min: V3; max: V3 } {
|
export function computeBoundingBox(objectType: ObjectType): { min: V3; max: V3 } {
|
||||||
@@ -37,3 +37,8 @@ export function depopulateRuntimeObject(object: RuntimeObjectInstance, _world: W
|
|||||||
const { cache: _cache, rb: _rb, ...result } = object;
|
const { cache: _cache, rb: _rb, ...result } = object;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function isGameObject(obj: ObjectInstance): obj is GameObjectInstance {
|
||||||
|
return 'version' in obj;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user