initial game scripting
This commit is contained in:
parent
53e68c2279
commit
4b00dbfc00
|
|
@ -0,0 +1,17 @@
|
||||||
|
import * as Blockly from "blockly";
|
||||||
|
import { javascriptGenerator } from "blockly/javascript";
|
||||||
|
|
||||||
|
Blockly.common.defineBlocksWithJsonArray([{
|
||||||
|
"type": "game_start_event",
|
||||||
|
"message0": "On game start",
|
||||||
|
"nextStatement": null,
|
||||||
|
"style": "event_hat",
|
||||||
|
"tooltip": "Runs when the game starts.",
|
||||||
|
"helpUrl": ""
|
||||||
|
}]);
|
||||||
|
|
||||||
|
javascriptGenerator.forBlock['game_start_event'] = function (_block, _generator) {
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export { };
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './gameStart';
|
||||||
|
export * from './objectTouchedMe';
|
||||||
|
export * from './playerTouchedMe';
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import * as Blockly from "blockly";
|
||||||
|
import { javascriptGenerator } from "blockly/javascript";
|
||||||
|
import { getObjectTypeDropDownOptions } from "../fieldTypes/objectType";
|
||||||
|
|
||||||
|
Blockly.Blocks['object_touch_start_event'] = {
|
||||||
|
init(this: Blockly.Block) {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField('When')
|
||||||
|
.appendField(
|
||||||
|
new Blockly.FieldDropdown(getObjectTypeDropDownOptions),
|
||||||
|
'OBJECT_TYPE',
|
||||||
|
)
|
||||||
|
.appendField('touches me');
|
||||||
|
this.setStyle('event_hat');
|
||||||
|
this.setNextStatement(true);
|
||||||
|
this.setTooltip('Fires when an object touches me');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
javascriptGenerator.forBlock['object_touch_start_event'] = function (_block, _generator) {
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export { };
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import * as Blockly from "blockly";
|
||||||
|
import { javascriptGenerator } from "blockly/javascript";
|
||||||
|
|
||||||
|
Blockly.Blocks['player_touch_start_event'] = {
|
||||||
|
init(this: Blockly.Block) {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField('When player touches me');
|
||||||
|
this.setStyle('event_hat');
|
||||||
|
this.setNextStatement(true);
|
||||||
|
this.setTooltip('Fires when player touches me');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
javascriptGenerator.forBlock['player_touch_start_event'] = function (_block, _generator) {
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export { };
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { state } from "../../state/rootState";
|
||||||
|
import { toJS } from "mobx";
|
||||||
|
|
||||||
|
export function getObjectTypeDropDownOptions(): [string, string][] {
|
||||||
|
const types = Object.values(toJS(state.world.data.objectTypes));
|
||||||
|
const values: [string, string][] = [
|
||||||
|
['Any object', '*'],
|
||||||
|
...types.map(t => [t.name, t.id] as [string, string]),
|
||||||
|
];
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function startGame() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import * as Blockly from "blockly";
|
||||||
|
import { javascriptGenerator } from "blockly/javascript";
|
||||||
|
|
||||||
|
export function generateCode(workspace: Blockly.WorkspaceSvg): string {
|
||||||
|
let result = '';
|
||||||
|
for (const block of workspace.getTopBlocks(true)) {
|
||||||
|
if (block.type.endsWith('_event')) {
|
||||||
|
let body = '';
|
||||||
|
let next = block.getNextBlock();
|
||||||
|
while (next) {
|
||||||
|
// opt_thisOnly=true prevents scrub_ from also chaining — we do it manually
|
||||||
|
body += javascriptGenerator.blockToCode(next, true);
|
||||||
|
next = next.getNextBlock();
|
||||||
|
}
|
||||||
|
body = javascriptGenerator.prefixLines(body, javascriptGenerator.INDENT);
|
||||||
|
|
||||||
|
const args = Array.from(block.getFields())
|
||||||
|
.filter(field => field.name !== undefined)
|
||||||
|
.map(field => `${field.name}: ${JSON.stringify(field.getValue())}`)
|
||||||
|
.join(', ');
|
||||||
|
|
||||||
|
//TODO use args
|
||||||
|
|
||||||
|
result += `onGameEvent('${block.type.replace(/_event$/, '')}', function(context) {\n${body}});\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './events';
|
||||||
|
export * from './values';
|
||||||
|
export * from './theme';
|
||||||
|
export * from './generateCode';
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import * as Blockly from "blockly";
|
||||||
|
|
||||||
|
export const gameTheme = Blockly.Theme.defineTheme(
|
||||||
|
'gameTheme',
|
||||||
|
{
|
||||||
|
name: 'GameTheme',
|
||||||
|
base: Blockly.Themes.Classic,
|
||||||
|
blockStyles: {
|
||||||
|
'event_hat': {
|
||||||
|
colourPrimary: '#FFAB19',
|
||||||
|
colourSecondary: '#CF8B17',
|
||||||
|
colourTertiary: '#CF8B17',
|
||||||
|
hat: 'cap',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import * as Blockly from "blockly";
|
||||||
|
import { javascriptGenerator, Order } from "blockly/javascript";
|
||||||
|
|
||||||
|
Blockly.Blocks['current_object_value'] = {
|
||||||
|
init(this: Blockly.Block) {
|
||||||
|
this.appendEndRowInput('NAME')
|
||||||
|
.appendField('current object');
|
||||||
|
this.setInputsInline(false)
|
||||||
|
this.setOutput(true, 'String');
|
||||||
|
this.setTooltip('asd');
|
||||||
|
this.setHelpUrl('asd');
|
||||||
|
this.setColour(315);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
javascriptGenerator.forBlock['current_object_value'] = function (_block, _generator) {
|
||||||
|
return ['object.id', Order.NONE];
|
||||||
|
};
|
||||||
|
|
||||||
|
export { };
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './currentObject';
|
||||||
|
|
@ -2,8 +2,8 @@ import { useEffect, useRef } from "react";
|
||||||
import { create } from "nipplejs";
|
import { create } from "nipplejs";
|
||||||
import { joystickValues } from "../joystickInput";
|
import { joystickValues } from "../joystickInput";
|
||||||
|
|
||||||
// const isTouch = navigator.maxTouchPoints > 0;
|
const isTouch = navigator.maxTouchPoints > 0;
|
||||||
const isTouch = true; // debug
|
// const isTouch = true; // debug
|
||||||
|
|
||||||
export function JoystickView() {
|
export function JoystickView() {
|
||||||
const moveZoneRef = useRef<HTMLDivElement>(null);
|
const moveZoneRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,19 @@ import * as Blockly from "blockly";
|
||||||
import type { ObjectType } from "../../types";
|
import type { ObjectType } from "../../types";
|
||||||
import './ScriptEditorView.scss';
|
import './ScriptEditorView.scss';
|
||||||
|
|
||||||
|
import '../../blockly';
|
||||||
|
import { gameTheme, generateCode } from '../../blockly';
|
||||||
|
|
||||||
const TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = {
|
const TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = {
|
||||||
kind: 'flyoutToolbox',
|
kind: 'flyoutToolbox',
|
||||||
contents: [
|
contents: [
|
||||||
{ kind: 'block', type: 'controls_if' },
|
|
||||||
{ kind: 'block', type: 'controls_repeat_ext' },
|
|
||||||
{ kind: 'block', type: 'logic_compare' },
|
|
||||||
{ kind: 'block', type: 'math_number' },
|
|
||||||
{ kind: 'block', type: 'math_arithmetic' },
|
|
||||||
{ kind: 'block', type: 'text' },
|
{ kind: 'block', type: 'text' },
|
||||||
{ kind: 'block', type: 'text_print' },
|
{ kind: 'block', type: 'text_print' },
|
||||||
|
{ kind: 'block', type: 'game_start_event' },
|
||||||
|
{ kind: 'block', type: 'object_touch_start_event' },
|
||||||
|
{ kind: 'block', type: 'player_touch_start_event' },
|
||||||
|
{ kind: 'block', type: 'current_object_value' },
|
||||||
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -25,32 +28,46 @@ type ScriptEditorViewProps = {
|
||||||
export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorViewProps) {
|
export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorViewProps) {
|
||||||
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 isLoadingRef = useRef(false);
|
const objectTypeRef = useRef(objectType);
|
||||||
|
objectTypeRef.current = objectType;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
const workspace = Blockly.inject(containerRef.current, { toolbox: TOOLBOX });
|
const workspace = Blockly.inject(containerRef.current, { toolbox: TOOLBOX, theme: gameTheme });
|
||||||
workspaceRef.current = workspace;
|
workspaceRef.current = workspace;
|
||||||
|
|
||||||
|
const listener = (e: Blockly.Events.Abstract) => {
|
||||||
|
if (e.isUiEvent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
const current = objectTypeRef.current;
|
||||||
|
const serialized = JSON.stringify(Blockly.serialization.workspaces.save(workspace));
|
||||||
|
current.program = serialized;
|
||||||
|
try {
|
||||||
|
current.javascript = generateCode(workspace);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
current.javascript = undefined;
|
||||||
|
}
|
||||||
|
console.log(current.javascript);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
workspace.addChangeListener(listener);
|
||||||
|
|
||||||
if (objectType.program) {
|
if (objectType.program) {
|
||||||
try {
|
try {
|
||||||
isLoadingRef.current = true;
|
Blockly.Events.disable();
|
||||||
Blockly.serialization.workspaces.load(JSON.parse(objectType.program), workspace);
|
Blockly.serialization.workspaces.load(JSON.parse(objectType.program), workspace);
|
||||||
} catch {
|
} catch {
|
||||||
// corrupt program, start fresh
|
// corrupt program, start fresh
|
||||||
} finally {
|
} finally {
|
||||||
isLoadingRef.current = false;
|
Blockly.Events.enable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const listener = (e: Blockly.Events.Abstract) => {
|
|
||||||
if (isLoadingRef.current || e.isUiEvent) return;
|
|
||||||
const serialized = JSON.stringify(Blockly.serialization.workspaces.save(workspace));
|
|
||||||
runInAction(() => { objectType.program = serialized; });
|
|
||||||
};
|
|
||||||
workspace.addChangeListener(listener);
|
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(() => Blockly.svgResize(workspace));
|
const resizeObserver = new ResizeObserver(() => Blockly.svgResize(workspace));
|
||||||
resizeObserver.observe(containerRef.current);
|
resizeObserver.observe(containerRef.current);
|
||||||
|
|
||||||
|
|
@ -66,16 +83,16 @@ export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorV
|
||||||
const workspace = workspaceRef.current;
|
const workspace = workspaceRef.current;
|
||||||
if (!workspace) return;
|
if (!workspace) return;
|
||||||
|
|
||||||
isLoadingRef.current = true;
|
|
||||||
workspace.clear();
|
|
||||||
if (objectType.program) {
|
|
||||||
try {
|
try {
|
||||||
|
Blockly.Events.disable();
|
||||||
|
workspace.clear();
|
||||||
|
if (objectType.program)
|
||||||
Blockly.serialization.workspaces.load(JSON.parse(objectType.program), workspace);
|
Blockly.serialization.workspaces.load(JSON.parse(objectType.program), workspace);
|
||||||
} catch {
|
} catch {
|
||||||
// corrupt program, start fresh
|
// corrupt program, start fresh
|
||||||
|
} finally {
|
||||||
|
Blockly.Events.enable();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
isLoadingRef.current = false;
|
|
||||||
}, [objectType.id]);
|
}, [objectType.id]);
|
||||||
|
|
||||||
return <div className="script-editor">
|
return <div className="script-editor">
|
||||||
|
|
|
||||||
|
|
@ -82,3 +82,10 @@ textarea {
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blockly appends its dropdown/widget overlays to document.body.
|
||||||
|
// They need a z-index above .overlay-panels (15000) to be visible.
|
||||||
|
.blocklyDropDownDiv,
|
||||||
|
.blocklyWidgetDiv {
|
||||||
|
z-index: 16000 !important;
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,54 @@
|
||||||
import type { Game, RuntimeGameScene, RuntimeScene, World } from "../types";
|
import type { Game, ObjectType, RuntimeGameScene, RuntimeObjectInstance, RuntimeScene, World } from "../types";
|
||||||
|
import type { GameEventHandler, GameEventContext } from "../types/runtime/gameBus";
|
||||||
import { clone } from "../utils";
|
import { clone } from "../utils";
|
||||||
import { populateRuntimeScene } from "../utils/runtime";
|
import { populateRuntimeScene } from "../utils/runtime";
|
||||||
|
|
||||||
|
type OwnedGameEventHandler = {
|
||||||
|
handler: GameEventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GameEventBus {
|
||||||
|
private handlers = new Map<string, OwnedGameEventHandler[]>();
|
||||||
|
|
||||||
|
on(event: string, handler: GameEventHandler): void {
|
||||||
|
console.log('subscribing to ' + event, handler);
|
||||||
|
let set = this.handlers.get(event);
|
||||||
|
if (!set) {
|
||||||
|
set = [];
|
||||||
|
this.handlers.set(event, set);
|
||||||
|
}
|
||||||
|
if (!set.some((h) => h.handler === handler))
|
||||||
|
set.push({ handler });
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event: string, handler: GameEventHandler): void {
|
||||||
|
console.log('unubscribing from ' + event, handler);
|
||||||
|
const handlers = this.handlers.get(event);
|
||||||
|
if (!handlers)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.handlers.set(event, handlers.filter((h) => h.handler !== handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(event: string, context: GameEventContext): void {
|
||||||
|
console.log('emitting ' + event);
|
||||||
|
console.log(Array.from(this.handlers.entries()));
|
||||||
|
this.handlers.get(event)
|
||||||
|
?.forEach((h) => h.handler(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class GameFactory {
|
export class GameFactory {
|
||||||
|
|
||||||
public static create(world: World): Game {
|
public static create(world: World): Game {
|
||||||
const scene = populateRuntimeScene(clone(world.initialScene), world);
|
const scene = populateRuntimeScene(clone(world.initialScene), world);
|
||||||
|
|
||||||
return {
|
const game = {
|
||||||
paused: false,
|
paused: false,
|
||||||
time: 0,
|
time: 0,
|
||||||
scene: GameFactory.initGameScene(scene),
|
scene: GameFactory.initGameScene(scene),
|
||||||
}
|
};
|
||||||
|
return game;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static load(): Game | undefined {
|
public static load(): Game | undefined {
|
||||||
|
|
@ -28,4 +65,33 @@ export class GameFactory {
|
||||||
private static initGameScene(scene: RuntimeScene): RuntimeGameScene {
|
private static initGameScene(scene: RuntimeScene): RuntimeGameScene {
|
||||||
return scene as RuntimeGameScene;
|
return scene as RuntimeGameScene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static evalGameScripts(
|
||||||
|
world: World,
|
||||||
|
scene: RuntimeGameScene,
|
||||||
|
eventBus: GameEventBus,
|
||||||
|
): void {
|
||||||
|
|
||||||
|
const internalBus = {
|
||||||
|
onGameEvent: eventBus.on.bind(eventBus),
|
||||||
|
offGameEvent: eventBus.off.bind(eventBus),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object.values(world.objectTypes)
|
||||||
|
.filter((ot) => ot.javascript)
|
||||||
|
.forEach((ot) => {
|
||||||
|
const otCode = eval(`(function gameScript({onGameEvent, offGameEvent}, object, objectType) {${ot.javascript}})`);
|
||||||
|
|
||||||
|
Object.values(scene.objects)
|
||||||
|
.filter((o) => o.typeId == ot.id)
|
||||||
|
.forEach((o) => {
|
||||||
|
otCode(internalBus, o, ot);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log('Error running game script:\n' + err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import { makeAutoObservable, reaction, toJS } from "mobx";
|
||||||
import type { WorldState } from "./worldState";
|
import type { WorldState } from "./worldState";
|
||||||
import type { Game, Pos3, V3, R3, RuntimeGameScene } from "../types";
|
import type { Game, Pos3, V3, R3, RuntimeGameScene } from "../types";
|
||||||
import type { CameraProps } from "@react-three/fiber";
|
import type { CameraProps } from "@react-three/fiber";
|
||||||
import { GameFactory } from "../model/gameFactory";
|
import { GameEventBus, GameFactory } from "../model/gameFactory";
|
||||||
|
import type { GameEventContext } from "../types/runtime/gameBus";
|
||||||
|
|
||||||
export class GameState {
|
export class GameState {
|
||||||
private readonly world: WorldState;
|
private readonly world: WorldState;
|
||||||
|
|
@ -11,6 +12,8 @@ export class GameState {
|
||||||
public time: number = 0;
|
public time: number = 0;
|
||||||
public scene: RuntimeGameScene;
|
public scene: RuntimeGameScene;
|
||||||
|
|
||||||
|
private eventBus: GameEventBus;
|
||||||
|
|
||||||
private startAutoSave() {
|
private startAutoSave() {
|
||||||
return reaction(
|
return reaction(
|
||||||
() => (this.asGame),
|
() => (this.asGame),
|
||||||
|
|
@ -31,13 +34,34 @@ export class GameState {
|
||||||
|
|
||||||
constructor(world: WorldState) {
|
constructor(world: WorldState) {
|
||||||
this.world = world;
|
this.world = world;
|
||||||
const game = GameFactory.create(toJS(this.world.data));
|
|
||||||
|
const rawWorld = toJS(this.world.data);
|
||||||
|
|
||||||
|
const game = GameFactory.create(rawWorld);
|
||||||
this.isPaused = game.paused;
|
this.isPaused = game.paused;
|
||||||
this.time = game.time;
|
this.time = game.time;
|
||||||
this.scene = game.scene;
|
this.scene = game.scene;
|
||||||
|
|
||||||
|
const eventBus = new GameEventBus();
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
GameFactory.evalGameScripts(rawWorld, game.scene, eventBus);
|
||||||
|
|
||||||
|
this.emitEvent('game_start');
|
||||||
|
|
||||||
this._stopAutoSave = this.startAutoSave();
|
this._stopAutoSave = this.startAutoSave();
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitEvent(event: string) {
|
||||||
|
this.eventBus.emit(
|
||||||
|
event,
|
||||||
|
{
|
||||||
|
world: this.world.data,
|
||||||
|
scene: this.scene,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get asGame(): Game {
|
public get asGame(): Game {
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,10 @@ export class RootState {
|
||||||
}
|
}
|
||||||
|
|
||||||
public startGame(): void {
|
public startGame(): void {
|
||||||
|
state.worldEditor.resetSelection();
|
||||||
if (this.game)
|
if (this.game)
|
||||||
this.stopGame();
|
this.stopGame();
|
||||||
this.game = new GameState(this.world),
|
this.game = new GameState(this.world);
|
||||||
state.worldEditor.resetSelectedObject();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public stopGame(): void {
|
public stopGame(): void {
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,6 @@ export class WorldEditorState {
|
||||||
|
|
||||||
public setSelection(value: Selection | undefined): void {
|
public setSelection(value: Selection | undefined): void {
|
||||||
this.selection = value;
|
this.selection = value;
|
||||||
console.log(JSON.stringify(this.selection));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setSelectedObject(value: Omit<SelectedObject, 'type'> | undefined): void {
|
public setSelectedObject(value: Omit<SelectedObject, 'type'> | undefined): void {
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ export class WorldState {
|
||||||
private saveData(data: World): void {
|
private saveData(data: World): void {
|
||||||
console.log('Saving world...');
|
console.log('Saving world...');
|
||||||
const stack = new Error('Saving world...').stack!.split('\n').slice(1);
|
const stack = new Error('Saving world...').stack!.split('\n').slice(1);
|
||||||
const { objectTypes, voxelTypes, ...debug } = toJS(data);
|
const { voxelTypes, initialScene, ...debug } = toJS(data);
|
||||||
console.dir({ stack, debug });
|
console.dir({ stack, debug });
|
||||||
WorldFactory.save(toJS(this.data));
|
WorldFactory.save(toJS(this.data));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export type ObjectType = {
|
||||||
name: string;
|
name: string;
|
||||||
voxels: Voxel[];
|
voxels: Voxel[];
|
||||||
program?: string;
|
program?: string;
|
||||||
|
javascript?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ObjectInstance = {
|
export type ObjectInstance = {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import type { Scene, World } from "../model";
|
||||||
|
|
||||||
|
export type GameEventContext = {
|
||||||
|
world: World;
|
||||||
|
scene: Scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GameEventHandler = (context: GameEventContext) => void;
|
||||||
|
|
||||||
|
export type GameBus = {
|
||||||
|
onGameEvent: (
|
||||||
|
event: string,
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
callback: GameEventHandler,
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue