diff --git a/editor/src/tools/workers/script.ts b/editor/src/tools/workers/script.ts index 909fe998c..fa36616bd 100644 --- a/editor/src/tools/workers/script.ts +++ b/editor/src/tools/workers/script.ts @@ -64,39 +64,52 @@ function extract(outputAbsolutePath: string) { try { const output = require(outputAbsolutePath) as any; - inspectorProperties = output.default?._VisibleInInspector ?? null; - - try { - function createMock(recorder = {}) { - return new Proxy(function () {}, { - get(_target, prop) { - if (!(prop in recorder)) { - recorder[prop] = createMock({}); - } - return recorder[prop]; - }, - set(_target, prop, value) { - recorder[prop] = value; - return true; - }, - apply() { - return createMock({}); - }, - construct() { - return createMock({}); - }, + if (output.default) { + inspectorProperties = output.default?._VisibleInInspector ?? null; + + try { + function createMock(recorder = {}) { + return new Proxy(function () {}, { + get(_target, prop) { + if (!(prop in recorder)) { + recorder[prop] = createMock({}); + } + return recorder[prop]; + }, + set(_target, prop, value) { + recorder[prop] = value; + return true; + }, + apply() { + return createMock({}); + }, + construct() { + return createMock({}); + }, + }); + } + + const mock = createMock(); + const instance = new output.default(mock); + + inspectorProperties?.forEach((value) => { + const defaultValue = instance[value.propertyKey]; + value.defaultValue = defaultValue?.asArray?.() ?? defaultValue; }); + } catch (e) { + // Catch silently. } + } else if (output.config) { + const keys = Object.keys(output.config); - const mock = createMock(); - const instance = new output.default(mock); + inspectorProperties = keys.map((key) => { + const property = output.config[key]; + property.propertyKey = key; + property.defaultValue = property.value?.asArray?.() ?? property.value; + delete property.value; - inspectorProperties?.forEach((value) => { - const defaultValue = instance[value.propertyKey]; - value.defaultValue = defaultValue?.asArray?.() ?? defaultValue; + return property; }); - } catch (e) { - // Catch silently. } } catch (e) { // Catch silently. diff --git a/tools/src/decorators/apply.ts b/tools/src/decorators/apply.ts index ce51a02ad..9cd4fb6f7 100644 --- a/tools/src/decorators/apply.ts +++ b/tools/src/decorators/apply.ts @@ -239,6 +239,7 @@ export function applyDecorators(scene: Scene, object: any, script: any, instance break; } } + break; } } }); diff --git a/tools/src/index.ts b/tools/src/index.ts index acc015555..f0827b56a 100644 --- a/tools/src/index.ts +++ b/tools/src/index.ts @@ -26,6 +26,8 @@ export * from "./decorators/inspector"; export * from "./decorators/events"; export * from "./decorators/sprite"; +export * from "./loading/script/config"; + export * from "./script"; export * from "./cinematic/parse"; diff --git a/tools/src/loading/script/apply.ts b/tools/src/loading/script/apply.ts index 253f45bd1..b7460d59d 100644 --- a/tools/src/loading/script/apply.ts +++ b/tools/src/loading/script/apply.ts @@ -12,6 +12,7 @@ import { applyDecorators } from "../../decorators/apply"; import { isAnyParticleSystem, isNode, isScene } from "../../tools/guards"; import { ScriptMap } from "../loader"; +import { applyConfig } from "./config"; /** * @internal @@ -42,6 +43,10 @@ export function _applyScriptsForObject(scene: Scene, object: any, scriptsMap: Sc Object.assign(observers, decoratorsResult?.observers ?? {}); } + if (exports.config) { + applyConfig(scene, exports.config, script.values, rootUrl); + } + if (result.onStart) { observers.onStartObserver = scene.onBeforeRenderObservable.addOnce(() => result.onStart!(object)); } diff --git a/tools/src/loading/script/config.ts b/tools/src/loading/script/config.ts new file mode 100644 index 000000000..5c0fdad32 --- /dev/null +++ b/tools/src/loading/script/config.ts @@ -0,0 +1,125 @@ +import { Scene } from "@babylonjs/core/scene"; +import { Material } from "@babylonjs/core/Materials/material"; +import { Color3, Color4 } from "@babylonjs/core/Maths/math.color"; +import { Texture } from "@babylonjs/core/Materials/Textures/texture"; +import { Vector2, Vector3 } from "@babylonjs/core/Maths/math.vector"; +import { NodeParticleSystemSet } from "@babylonjs/core/Particles/Node/nodeParticleSystemSet"; + +import { getSoundById } from "../../tools/sound"; + +import { + VisibleInInspectorDecoratorColor3Configuration, + VisibleInInspectorDecoratorColor4Configuration, + VisibleInInspectorDecoratorConfiguration, + VisibleInInspectorDecoratorEntityConfiguration, + VisibleInInspectorDecoratorNumberConfiguration, + VisibleInInspectorDecoratorStringConfiguration, + VisibleInInspectorDecoratorTextureConfiguration, + VisibleInInspectorDecoratorVector2Configuration, + VisibleInInspectorDecoratorVector3Configuration, + VisibleInspectorDecoratorAssetConfiguration, +} from "../../decorators/inspector"; + +import { scriptAssetsCache } from "./preload"; + +export type ScriptConfig = { + [key: string]: { + label: string; + value?: any; + configuration: + | VisibleInInspectorDecoratorConfiguration + | VisibleInInspectorDecoratorStringConfiguration + | VisibleInInspectorDecoratorNumberConfiguration + | VisibleInInspectorDecoratorVector2Configuration + | VisibleInInspectorDecoratorVector3Configuration + | VisibleInInspectorDecoratorColor3Configuration + | VisibleInInspectorDecoratorColor4Configuration + | VisibleInInspectorDecoratorEntityConfiguration + | VisibleInInspectorDecoratorTextureConfiguration + | VisibleInspectorDecoratorAssetConfiguration; + }; +}; + +export function applyConfig(scene: Scene, instance: ScriptConfig, config: ScriptConfig, rootUrl: string) { + const keys = Object.keys(instance); + keys.forEach((key) => { + const exportedValue = config[key]; + const originalValue = instance[key]; + + if (!exportedValue || !originalValue) { + return; + } + + switch (originalValue.configuration.type) { + case "number": + case "boolean": + case "keymap": + case "string": + originalValue.value = exportedValue.value; + break; + + case "vector2": + originalValue.value = Vector2.FromArray(exportedValue.value); + break; + case "vector3": + originalValue.value = Vector3.FromArray(exportedValue.value); + break; + + case "color3": + originalValue.value = Color3.FromArray(exportedValue.value); + break; + case "color4": + originalValue.value = Color4.FromArray(exportedValue.value); + break; + + case "entity": + const entityType = (originalValue.configuration as VisibleInInspectorDecoratorEntityConfiguration).entityType; + switch (entityType) { + case "node": + originalValue.value = scene.getNodeById(exportedValue.value) ?? null; + break; + case "animationGroup": + originalValue.value = scene.getAnimationGroupByName(exportedValue.value) ?? null; + break; + case "sound": + originalValue.value = getSoundById(exportedValue.value, scene); + break; + case "particleSystem": + originalValue.value = scene.particleSystems?.find((ps) => ps.id === exportedValue.value) ?? null; + break; + } + break; + + case "texture": + if (exportedValue.value) { + originalValue.value = Texture.Parse(exportedValue.value, scene, rootUrl); + } + break; + + case "asset": + if (exportedValue.value) { + const assetType = (originalValue.configuration as VisibleInspectorDecoratorAssetConfiguration).assetType; + const data = scriptAssetsCache.get(exportedValue.value); + + switch (assetType) { + case "json": + case "gui": + case "scene": + case "navmesh": + originalValue.value = data; + break; + + case "nodeParticleSystemSet": + const npss = NodeParticleSystemSet.Parse(data); + originalValue.value = npss; + break; + + case "material": + originalValue.value = Material.Parse(data, scene, rootUrl); + break; + } + } + break; + } + }); +} diff --git a/tools/src/script.ts b/tools/src/script.ts index 67f1402e6..6ac8df58a 100644 --- a/tools/src/script.ts +++ b/tools/src/script.ts @@ -1,7 +1,14 @@ +import { ScriptConfig } from "./loading/script/config"; + /** * Defines the interface that can be implemented by scripts attached to nodes in the editor. */ export interface IScript { + /** + * Optional configuration object that can be used to configure the script. + */ + config?: ScriptConfig; + /** * Method called when the script starts. This method is called only once. */