Compare commits

..

No commits in common. "ff5528008b30aed5ec58951b129ca050079e058d" and "b017beca2d8808cc1b61776b908ffd81c6774f54" have entirely different histories.

12 changed files with 34 additions and 135 deletions

View File

@ -15,7 +15,7 @@ Blockly.Blocks['physics_apply_impulse_action'] = {
this.setInputsInline(true) this.setInputsInline(true)
this.setPreviousStatement(true, null); this.setPreviousStatement(true, null);
this.setNextStatement(true, null); this.setNextStatement(true, null);
this.setTooltip('Push me in a direction'); this.setTooltip('Push me to a position');
this.setColour(150); this.setColour(150);
} }
}; };

View File

@ -7,4 +7,3 @@ export * from './position';
export * from './direction'; export * from './direction';
export * from './objectById'; export * from './objectById';
export * from './directionToPosition'; export * from './directionToPosition';
export * from './playerObject';

View File

@ -1,19 +0,0 @@
import * as Blockly from "blockly";
import { javascriptGenerator, Order } from "blockly/javascript";
Blockly.Blocks['player_object_value'] = {
init(this: Blockly.Block) {
this.appendEndRowInput()
.appendField('Player');
this.setInputsInline(false)
this.setOutput(true, 'Object');
this.setTooltip('Returns object that is controlled by player');
this.setColour(315);
}
};
javascriptGenerator.forBlock['player_object_value'] = function (_block, _generator) {
return ['(scene.playerObjectId?scene.objects[scene.playerObjectId]:undefined)', Order.ATOMIC];
};
export { };

View File

@ -69,7 +69,7 @@ export const PlayerObjectView = observer(function ({ object, objectType }: Playe
useEffect(() => { useEffect(() => {
const canvas = gl.domElement; const canvas = gl.domElement;
const onClick = () => { if (document.pointerLockElement !== canvas) canvas.requestPointerLock()?.catch(() => {}); }; const onClick = () => canvas.requestPointerLock();
const onMouseMove = (e: MouseEvent) => { const onMouseMove = (e: MouseEvent) => {
if (document.pointerLockElement !== canvas) return; if (document.pointerLockElement !== canvas) return;
mouseRef.current.x += e.movementX; mouseRef.current.x += e.movementX;

View File

@ -1,6 +1,6 @@
.menu { .menu {
user-select: none; user-select: none;
font-size: 16px; font-size: 13px;
& details { & details {
&>summary { &>summary {
@ -11,6 +11,7 @@
} }
display: flex; display: flex;
padding: 8px 8px;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;
white-space: nowrap; white-space: nowrap;
@ -29,26 +30,12 @@
} }
&>.title { &>.title {
padding: 8px;
flex: 1; flex: 1;
overflow-x: hidden;
text-overflow: ellipsis;
}
&>.actions {
display: flex;
&>* {
padding: 8px;
}
&>*:hover {
background: rgba(255, 255, 255, 0.08);
}
} }
& svg { & svg {
vertical-align: middle; vertical-align: middle;
margin: 2px;
box-sizing: border-box; box-sizing: border-box;
} }

View File

@ -4,6 +4,7 @@ import { useMemo, useRef, type RefObject } from "react";
import type { Group } from "three"; import type { Group } 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 type { ThreeEvent } from "@react-three/fiber";
import { state } from "../state"; import { state } from "../state";
import { nextObjectEditMode } from "../state/worldEditorState"; import { nextObjectEditMode } from "../state/worldEditorState";
import { ObjectViewInternal, type ObjectViewInternalHandle } from "./ObjectViewInternal"; import { ObjectViewInternal, type ObjectViewInternalHandle } from "./ObjectViewInternal";

View File

@ -63,12 +63,13 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props
return reaction( return reaction(
() => gameObj.pendingActions.impulse, () => gameObj.pendingActions.impulse,
(impulse) => { (impulse) => {
if (!impulse || !rbRef.current) if (!impulse) return;
return; if (!rbRef.current) { console.warn('applyImpulse: rbRef is null for', gameObj.id); return; }
let { direction, amplitude } = impulse; let { direction, amplitude } = impulse;
amplitude *= 100; amplitude *= 100;
const v = { x: direction[0] * amplitude, y: direction[1] * amplitude, z: direction[2] * amplitude }; const v = { x: direction[0] * amplitude, y: direction[1] * amplitude, z: direction[2] * amplitude };
console.log('applyImpulse', gameObj.id, v, 'bodyType', rbRef.current.bodyType());
rbRef.current.applyImpulse(v, true); rbRef.current.applyImpulse(v, true);
runInAction(() => { runInAction(() => {
gameObj.pendingActions.impulse = undefined; gameObj.pendingActions.impulse = undefined;

View File

@ -36,7 +36,6 @@ const TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = {
{ kind: 'block', type: 'dpos_value' }, { kind: 'block', type: 'dpos_value' },
{ kind: 'block', type: 'direction_to_position_value' }, { kind: 'block', type: 'direction_to_position_value' },
{ kind: 'block', type: 'object_by_id_value' }, { kind: 'block', type: 'object_by_id_value' },
{ kind: 'block', type: 'player_object_value' },
], ],
}, },
{ {

View File

@ -38,5 +38,3 @@ export const DEFAULT_VOXEL_TYPES = {
[water.id]: water, [water.id]: water,
[glass.id]: glass, [glass.id]: glass,
} }
export const DEFAULT_VOXEL_TYPE = stone;

View File

@ -29,6 +29,7 @@ export class GameEventBus {
emit(event: string, context: GameEventContext): void { emit(event: string, context: GameEventContext): void {
console.log('emitting ' + event); console.log('emitting ' + event);
console.log(Array.from(this.handlers.entries()));
this.handlers.get(event) this.handlers.get(event)
?.forEach((handler) => handler(context)); ?.forEach((handler) => handler(context));
} }

View File

@ -1,7 +1,7 @@
import { makeAutoObservable } from "mobx"; import { makeAutoObservable } from "mobx";
import { state } from "./rootState"; import { state } from "./rootState";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { IconRun, IconTrash, IconPlus, IconCubePlus, IconCubeOff } from '@tabler/icons-react'; import { IconRun } from '@tabler/icons-react';
export type MenuNodeAction = { export type MenuNodeAction = {
id: string; id: string;
@ -26,83 +26,51 @@ export class MenuState {
} }
private get editorObjectTypesMenu(): MenuNode[] { private get editorObjectTypesMenu(): MenuNode[] {
const editor = state.worldEditor; const scene = state.worldEditor.scene;
const scene = editor.scene;
return Object.values(state.world.data.objectTypes) return Object.values(state.world.data.objectTypes)
.map((ot) => { .map((ot) => ({
const children = Object.values(scene.objects) id: `ot-${ot.id}`,
title: ot.name,
onClick: () => { state.worldEditor.setSelectedObjectType({ id: ot.id, editMode: 'scripts' }) },
selected: () => state.worldEditor.selection?.type === 'objectType' && state.worldEditor.selection?.id === ot.id,
children: Object.values(scene.objects)
.filter((o) => o.typeId === ot.id) .filter((o) => o.typeId === ot.id)
.map((o, idx) => { .map((o) => {
const isPlayer = scene.playerObjectId === o.id; const isPlayer = scene.playerObjectId === o.id;
const actions: MenuNodeAction[] = []; const actions: MenuNodeAction[] = [];
if (!isPlayer) { if (!isPlayer)
actions.push({ actions.push({
id: 'delete-object-instance', id: 'control-by-player',
content: <IconTrash size="1em" />,
tooltip: 'Delete the object',
onClick: () => { editor.deleteObject(o.id); },
});
actions.push({
id: 'control-object-instance-by-player',
content: <IconRun size="1em" />, content: <IconRun size="1em" />,
tooltip: 'Mark as player', tooltip: 'Mark as player',
onClick: () => { scene.playerObjectId = o.id; }, onClick: () => { state.markObjectAsPlayer(o); },
}); });
}
return { return {
id: `o-${o.id}`, id: `o-${o.id}`,
title: <> title: <>
{isPlayer && <IconRun size="1em" />} {isPlayer && <IconRun size="1em" />}
{`${ot.name} ${idx + 1}`} {o.id}
</>, </>,
className: isPlayer ? 'player-controlled-object' : undefined, className: isPlayer ? 'player-controlled-object' : undefined,
actions, actions,
onClick: () => { state.worldEditor.setSelectedObject({ id: o.id, editMode: 'translate' }) }, onClick: () => { state.worldEditor.setSelectedObject({ id: o.id, editMode: 'translate' }) },
selected: () => state.worldEditor.selection?.type === 'object' && state.worldEditor.selection?.id === o.id, selected: () => state.worldEditor.selection?.type === 'object' && state.worldEditor.selection?.id === o.id,
} as MenuNode; } as MenuNode;
}); })
} as MenuNode));
return {
id: `ot-${ot.id}`,
title: `${ot.name} (${children.length ? children.length : 'no'} instance${children.length != 1 ? 's' : ''})`,
actions: [
{
id: 'create-object-instance',
content: <IconPlus size="1em" />,
tooltip: 'Create new object instance',
onClick: () => { editor.addObjectInstanceAtRandomPosition(ot.id); },
},
{
id: 'delete-object-type',
content: <IconCubeOff size="1em" />,
tooltip: 'Delete object type',
onClick: () => { editor.deleteObjectType(ot.id); },
},
],
onClick: () => { editor.setSelectedObjectType({ id: ot.id, editMode: 'scripts' }) },
selected: () => editor.selection?.type === 'objectType' && editor.selection?.id === ot.id,
children,
} as MenuNode
});
} }
private get editorMenu(): MenuNode[] { private get editorMenu(): MenuNode[] {
return [ return [
{ {
id: 'editor-scene-menu', id: 'editor-objects-menu',
title: 'Scene', title: 'Objects',
onClick: () => { state.worldEditor.resetSelection() }, onClick: () => { state.worldEditor.resetSelection() },
selected: () => !state.worldEditor.selection, selected: () => !state.worldEditor.selection,
children: this.editorObjectTypesMenu, children: this.editorObjectTypesMenu,
actions: [{
id: 'create-object-type',
content: <IconCubePlus size="1em" />,
tooltip: 'Create new object type',
onClick: () => { state.worldEditor.addObjectType(); },
}],
} }
] ]
} }

View File

@ -1,11 +1,10 @@
import { makeAutoObservable, runInAction } from "mobx"; import { makeAutoObservable, runInAction } from "mobx";
import type { WorldState } from "./worldState"; import type { WorldState } from "./worldState";
import { type ObjectInstance, type Pos3, type R3, type V3, type RuntimeScene, type ObjectType } from "../types"; import { type ObjectInstance, type Pos3, type R3, type V3, type RuntimeScene } from "../types";
import { createObjectInstance } from "../utils/object"; import { createObjectInstance } from "../utils/object";
import { randomId } from "../utils"; import { randomId } from "../utils";
import { state } from "./rootState"; import { state } from "./rootState";
import { populateRuntimeObject } from "../utils/runtime"; import { populateRuntimeObject } from "../utils/runtime";
import { DEFAULT_VOXEL_TYPE } from "../model/defaultVoxelTypes";
export const ObjectEditModeEnum = [ export const ObjectEditModeEnum = [
'translate', 'translate',
@ -117,14 +116,14 @@ export class WorldEditorState {
}); });
} }
public addObjectInstanceAtRandomPosition(typeId: string): ObjectInstance { public addObjectCloneAtRandomPosition(typeId: string): ObjectInstance {
return this.addObjectInstance( return this.addObjectClone(
typeId, typeId,
{ x: Math.random() * 3, y: Math.random() * 3, z: Math.random() * 1 }, { x: Math.random() * 3, y: Math.random() * 3, z: Math.random() * 1 },
); );
} }
public addObjectInstance( public addObjectClone(
typeId: string, typeId: string,
pos: { x: number; y: number; z: number; }, pos: { x: number; y: number; z: number; },
): ObjectInstance { ): ObjectInstance {
@ -134,42 +133,7 @@ export class WorldEditorState {
this.scene.objects[obj.id] = obj; this.scene.objects[obj.id] = obj;
this.setSelectedObject({ id: obj.id, editMode: 'translate' });
return obj; return obj;
} }
public deleteObject(objectId: string) {
if (this.selection?.type === 'object' && this.selection.id === objectId)
this.resetSelection();
delete (this.scene.objects[objectId]);
}
public addObjectType(): ObjectType {
const objectType: ObjectType = {
id: randomId(),
name: 'Unnamed',
voxels: [
{
typeId: DEFAULT_VOXEL_TYPE.id,
position: [0, 0, 0],
}
],
};
this.world.data.objectTypes[objectType.id] = objectType;
this.setSelectedObjectType({ id: objectType.id, editMode: 'scripts' });
return objectType;
}
public deleteObjectType(typeId: string) {
if (this.selection?.type === 'objectType' && this.selection.id === typeId)
this.resetSelection();
for (const id in this.scene.objects)
if (this.scene.objects[id].typeId === typeId)
this.deleteObject(id);
delete (this.world.data.objectTypes[typeId])
}
} }