game_start_event and console_log_action

This commit is contained in:
azykov@mail.ru 2026-06-05 10:47:57 +03:00
parent 4b00dbfc00
commit b3ed15fa30
No known key found for this signature in database
12 changed files with 143 additions and 36 deletions

View File

@ -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 { };

View File

@ -0,0 +1 @@
export * from './consoleLog';

View File

@ -1,4 +1,5 @@
export * from './events';
export * from './values';
export * from './actions';
export * from './theme';
export * from './generateCode';

View File

@ -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);
}
};

View File

@ -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 { };

View File

@ -1 +1,2 @@
export * from './currentObject';
export * from './currentObjectType';

View File

@ -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' },
],
},
],
};

View File

@ -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<string, OwnedGameEventHandler[]>();
private handlers = new Map<string, GameEventHandler[]>();
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 });
})
});
}

View File

@ -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');
}
}
}

View File

@ -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 extends ObjectInstance> = T & ObjectInstanceRuntimeData;

View File

@ -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<string, unknown>,
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;
}

View File

@ -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 };
}
}