From b3ed15fa30830f4a1ee98b00650dcdf0fd5e8f93 Mon Sep 17 00:00:00 2001 From: "azykov@mail.ru" Date: Fri, 5 Jun 2026 10:47:57 +0300 Subject: [PATCH] game_start_event and console_log_action --- src/blockly/actions/consoleLog.ts | 23 ++++++++++ src/blockly/actions/index.ts | 1 + src/blockly/index.ts | 1 + src/blockly/values/currentObject.ts | 5 +-- src/blockly/values/currentObjectType.ts | 19 ++++++++ src/blockly/values/index.ts | 1 + .../scriptEditor/ScriptEditorView.tsx | 45 +++++++++++++++---- src/model/gameFactory.ts | 27 ++++++----- src/state/gameState.ts | 10 +++-- src/types/model/runtime.ts | 4 ++ src/types/runtime/gameBus.ts | 19 ++++---- src/utils/runtime/objectApi.ts | 24 ++++++++++ 12 files changed, 143 insertions(+), 36 deletions(-) create mode 100644 src/blockly/actions/consoleLog.ts create mode 100644 src/blockly/actions/index.ts create mode 100644 src/blockly/values/currentObjectType.ts create mode 100644 src/utils/runtime/objectApi.ts diff --git a/src/blockly/actions/consoleLog.ts b/src/blockly/actions/consoleLog.ts new file mode 100644 index 0000000..0ca6088 --- /dev/null +++ b/src/blockly/actions/consoleLog.ts @@ -0,0 +1,23 @@ +import * as Blockly from "blockly"; +import { javascriptGenerator, Order } from "blockly/javascript"; + +Blockly.Blocks['console_log_action'] = { + init(this: Blockly.Block) { + this.appendValueInput('VALUE') + .setAlign(Blockly.inputs.Align.RIGHT) + .appendField('print'); + this.setInputsInline(false) + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setTooltip('Print value to console'); + this.setHelpUrl(''); + this.setColour(150); + } +}; + +javascriptGenerator.forBlock['console_log_action'] = function (block, generator) { + const valueValue = generator.valueToCode(block, 'VALUE', Order.ATOMIC); + return `console.log(${valueValue});\n`; +}; + +export { }; diff --git a/src/blockly/actions/index.ts b/src/blockly/actions/index.ts new file mode 100644 index 0000000..6f414f9 --- /dev/null +++ b/src/blockly/actions/index.ts @@ -0,0 +1 @@ +export * from './consoleLog'; \ No newline at end of file diff --git a/src/blockly/index.ts b/src/blockly/index.ts index 1dbdba7..773874b 100644 --- a/src/blockly/index.ts +++ b/src/blockly/index.ts @@ -1,4 +1,5 @@ export * from './events'; export * from './values'; +export * from './actions'; export * from './theme'; export * from './generateCode'; diff --git a/src/blockly/values/currentObject.ts b/src/blockly/values/currentObject.ts index 4d29544..9cdc4ca 100644 --- a/src/blockly/values/currentObject.ts +++ b/src/blockly/values/currentObject.ts @@ -4,11 +4,10 @@ import { javascriptGenerator, Order } from "blockly/javascript"; Blockly.Blocks['current_object_value'] = { init(this: Blockly.Block) { this.appendEndRowInput('NAME') - .appendField('current object'); + .appendField('Current object'); this.setInputsInline(false) this.setOutput(true, 'String'); - this.setTooltip('asd'); - this.setHelpUrl('asd'); + this.setTooltip('Returns id of current object instance'); this.setColour(315); } }; diff --git a/src/blockly/values/currentObjectType.ts b/src/blockly/values/currentObjectType.ts new file mode 100644 index 0000000..fe31879 --- /dev/null +++ b/src/blockly/values/currentObjectType.ts @@ -0,0 +1,19 @@ +import * as Blockly from "blockly"; +import { javascriptGenerator, Order } from "blockly/javascript"; + +Blockly.Blocks['current_object_type_value'] = { + init(this: Blockly.Block) { + this.appendEndRowInput('NAME') + .appendField('Current object type'); + this.setInputsInline(false) + this.setOutput(true, 'String'); + this.setTooltip('Returns id of current object type'); + this.setColour(315); + } +}; + +javascriptGenerator.forBlock['current_object_type_value'] = function (_block, _generator) { + return ['objectType.id', Order.NONE]; +}; + +export { }; diff --git a/src/blockly/values/index.ts b/src/blockly/values/index.ts index ed55ff5..d3da9f4 100644 --- a/src/blockly/values/index.ts +++ b/src/blockly/values/index.ts @@ -1 +1,2 @@ export * from './currentObject'; +export * from './currentObjectType'; diff --git a/src/components/scriptEditor/ScriptEditorView.tsx b/src/components/scriptEditor/ScriptEditorView.tsx index 046bc21..815510f 100644 --- a/src/components/scriptEditor/ScriptEditorView.tsx +++ b/src/components/scriptEditor/ScriptEditorView.tsx @@ -9,15 +9,44 @@ import '../../blockly'; import { gameTheme, generateCode } from '../../blockly'; const TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = { - kind: 'flyoutToolbox', + kind: 'categoryToolbox', contents: [ - { kind: 'block', type: 'text' }, - { 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' }, - + { + kind: 'category', + name: 'Events', + colour: '180', + contents: [ + { kind: 'block', type: 'game_start_event' }, + { kind: 'block', type: 'object_touch_start_event' }, + { kind: 'block', type: 'player_touch_start_event' }, + ], + }, + { + kind: 'category', + name: 'Values', + colour: '260', + contents: [ + { kind: 'block', type: 'current_object_value' }, + { kind: 'block', type: 'current_object_type_value' }, + ], + }, + { + kind: 'category', + name: 'Actions', + colour: '20', + contents: [ + { kind: 'block', type: 'console_log_action' }, + { kind: 'block', type: 'text_print' }, + ], + }, + { + kind: 'category', + name: 'Text', + colour: '160', + contents: [ + { kind: 'block', type: 'text' }, + ], + }, ], }; diff --git a/src/model/gameFactory.ts b/src/model/gameFactory.ts index a871257..611a39e 100644 --- a/src/model/gameFactory.ts +++ b/src/model/gameFactory.ts @@ -1,14 +1,11 @@ -import type { Game, ObjectType, RuntimeGameScene, RuntimeObjectInstance, RuntimeScene, World } from "../types"; -import type { GameEventHandler, GameEventContext } from "../types/runtime/gameBus"; +import type { Game, RuntimeGameScene, RuntimeScene, World } from "../types"; +import type { GameEventHandler, GameEventContext, InternalGameEventBus } from "../types/runtime/gameBus"; import { clone } from "../utils"; import { populateRuntimeScene } from "../utils/runtime"; - -type OwnedGameEventHandler = { - handler: GameEventHandler; -} +import { ObjectApi } from "../utils/runtime/objectApi"; export class GameEventBus { - private handlers = new Map(); + private handlers = new Map(); on(event: string, handler: GameEventHandler): void { console.log('subscribing to ' + event, handler); @@ -17,8 +14,8 @@ export class GameEventBus { set = []; this.handlers.set(event, set); } - if (!set.some((h) => h.handler === handler)) - set.push({ handler }); + if (!set.includes(handler)) + set.push(handler); } off(event: string, handler: GameEventHandler): void { @@ -27,14 +24,14 @@ export class GameEventBus { if (!handlers) return; - this.handlers.set(event, handlers.filter((h) => h.handler !== handler)); + this.handlers.set(event, handlers.filter((h) => h !== 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)); + ?.forEach((handler) => handler(context)); } } @@ -70,9 +67,10 @@ export class GameFactory { world: World, scene: RuntimeGameScene, eventBus: GameEventBus, + ): void { - const internalBus = { + const internalBus: InternalGameEventBus = { onGameEvent: eventBus.on.bind(eventBus), offGameEvent: eventBus.off.bind(eventBus), }; @@ -81,12 +79,13 @@ export class GameFactory { Object.values(world.objectTypes) .filter((ot) => ot.javascript) .forEach((ot) => { - const otCode = eval(`(function gameScript({onGameEvent, offGameEvent}, object, objectType) {${ot.javascript}})`); + 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) => { - otCode(internalBus, o, ot); + otCode(internalBus, { api, o, ot }); }) }); } diff --git a/src/state/gameState.ts b/src/state/gameState.ts index 912d2ac..40c65b0 100644 --- a/src/state/gameState.ts +++ b/src/state/gameState.ts @@ -3,13 +3,13 @@ import type { WorldState } from "./worldState"; import type { Game, Pos3, V3, R3, RuntimeGameScene } from "../types"; import type { CameraProps } from "@react-three/fiber"; import { GameEventBus, GameFactory } from "../model/gameFactory"; -import type { GameEventContext } from "../types/runtime/gameBus"; export class GameState { private readonly world: WorldState; public isPaused: boolean = false; public time: number = 0; + public isFirstTick: boolean; public scene: RuntimeGameScene; private eventBus: GameEventBus; @@ -40,14 +40,13 @@ export class GameState { const game = GameFactory.create(rawWorld); this.isPaused = game.paused; this.time = game.time; + this.isFirstTick = true; 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(); makeAutoObservable( this, @@ -145,5 +144,10 @@ export class GameState { return; this.time += deltaTime; + + if (this.isFirstTick) { + this.isFirstTick = false; + this.emitEvent('game_start'); + } } } \ No newline at end of file diff --git a/src/types/model/runtime.ts b/src/types/model/runtime.ts index f83bcb4..920ee3a 100644 --- a/src/types/model/runtime.ts +++ b/src/types/model/runtime.ts @@ -1,3 +1,4 @@ +import type { V3 } from "../3d"; import type { VoxelGroup } from "../runtime"; import type { ObjectInstance } from "./object"; @@ -6,6 +7,9 @@ export type ObjectInstanceRuntimeData = { voxelGroups: VoxelGroup[]; colliderMesh: [Float32Array, Uint32Array] | null; }; + pendingActions: { + impulse: { direction: V3, amplitude: number }; + } } export type Runtime = T & ObjectInstanceRuntimeData; diff --git a/src/types/runtime/gameBus.ts b/src/types/runtime/gameBus.ts index a59bf9e..aa2a631 100644 --- a/src/types/runtime/gameBus.ts +++ b/src/types/runtime/gameBus.ts @@ -1,16 +1,19 @@ -import type { Scene, World } from "../model"; +import type { ObjectType, RuntimeGameObjectInstance, Scene, World } from "../model"; export type GameEventContext = { world: World; scene: Scene; } -export type GameEventHandler = (context: GameEventContext) => void; +// export type GameObjectEventContext = GameEventContext & { +// object: RuntimeGameObjectInstance; +// objectType: ObjectType; +// } -export type GameBus = { - onGameEvent: ( - event: string, - params: Record, - callback: GameEventHandler, - ) => void; +export type GameEventHandler = (context: GameEventContext) => void; +// export type GameObjectEventHandler = (context: GameObjectEventContext) => void; + +export type InternalGameEventBus = { + onGameEvent: (event: string, handler: GameEventHandler) => void; + offGameEvent: (event: string, handler: GameEventHandler) => void; } \ No newline at end of file diff --git a/src/utils/runtime/objectApi.ts b/src/utils/runtime/objectApi.ts new file mode 100644 index 0000000..d47b2c3 --- /dev/null +++ b/src/utils/runtime/objectApi.ts @@ -0,0 +1,24 @@ +import type { ObjectType, RuntimeGameObjectInstance, RuntimeGameScene, V3, World } from "../../types"; + +export class ObjectApi { + private object: RuntimeGameObjectInstance; + private obectType: ObjectType; + private world: World; + private scene: RuntimeGameScene; + + constructor( + object: RuntimeGameObjectInstance, + obectType: ObjectType, + world: World, + scene: RuntimeGameScene, + ) { + this.object = object; + this.obectType = obectType; + this.world = world; + this.scene = scene; + } + + public applyImpulse(direction: V3, amplitude: number) { + this.object.pendingActions.impulse = { direction, amplitude }; + } +}