From 80cb6d697cadada4dce933952e8036d0b05db328 Mon Sep 17 00:00:00 2001 From: D8H Date: Tue, 18 Oct 2022 13:36:48 +0200 Subject: [PATCH] Handle custom objects at runtime (#4294) * Don't show in changelog --- Core/GDCore/IDE/ProjectStripper.cpp | 29 +- .../Project/CustomObjectConfiguration.cpp | 2 +- Core/tests/WholeProjectRefactorer.cpp | 2 + .../AnchorBehavior/anchorruntimebehavior.ts | 70 +- .../bbtextruntimeobject-pixi-renderer.ts | 11 +- Extensions/BBText/bbtextruntimeobject.ts | 22 +- .../bitmaptextruntimeobject-pixi-renderer.ts | 17 +- .../BitmapText/bitmaptextruntimeobject.ts | 20 +- Extensions/DebuggerTools/debuggertools.ts | 14 +- .../destroyoutsideruntimebehavior.ts | 14 +- Extensions/DialogueTree/dialoguetools.ts | 6 +- .../draggableruntimebehavior.ts | 148 ++-- .../dummyruntimebehavior.ts | 8 +- .../dummyruntimeobject-pixi-renderer.ts | 8 +- .../ExampleJsExtension/dummyruntimeobject.ts | 13 +- .../dummywithshareddataruntimebehavior.ts | 14 +- .../examplejsextensiontools.ts | 12 +- Extensions/FileSystem/filesystemtools.ts | 69 +- Extensions/Inventory/inventorytools.ts | 120 ++- Extensions/Leaderboards/leaderboardstools.ts | 1 + .../Lighting/lightobstacleruntimebehavior.ts | 22 +- .../lightruntimeobject-pixi-renderer.ts | 12 +- Extensions/Lighting/lightruntimeobject.ts | 4 +- Extensions/LinkedObjects/linkedobjects.ts | 38 +- .../panelspriteruntimeobject-pixi-renderer.ts | 21 +- .../panelspriteruntimeobject.ts | 29 +- .../particleemitterobject-pixi-renderer.ts | 23 +- .../ParticleSystem/particleemitterobject.ts | 34 +- .../pathfindingobstacleruntimebehavior.ts | 40 +- .../pathfindingruntimebehavior.ts | 23 +- .../legacypathfindingruntimebehavior.spec.js | 40 +- .../physics2runtimebehavior.ts | 15 +- .../platformerobjectruntimebehavior.ts | 14 +- .../platformruntimebehavior.ts | 33 +- .../playerauthenticationtools.ts | 1 + ...shapepainterruntimeobject-pixi-renderer.ts | 4 +- .../shapepainterruntimeobject.ts | 28 +- Extensions/Screenshot/screenshottools.ts | 6 +- Extensions/Shopify/shopifytools.ts | 46 +- Extensions/SpatialSound/spatialsoundtools.ts | 8 +- Extensions/SystemInfo/systeminfotools.ts | 12 +- .../textentryruntimeobject-pixi-renderer.ts | 4 +- .../TextEntryObject/textentryruntimeobject.ts | 12 +- .../textinputruntimeobject-pixi-renderer.ts | 65 +- .../TextInput/textinputruntimeobject.ts | 15 +- .../textruntimeobject-pixi-renderer.ts | 6 +- Extensions/TextObject/textruntimeobject.ts | 25 +- Extensions/TileMap/TileMapRuntimeManager.ts | 26 +- .../collision/TileMapCollisionMaskRenderer.ts | 4 +- .../tilemapcollisionmaskruntimeobject.ts | 20 +- .../tilemapruntimeobject-pixi-renderer.ts | 8 +- Extensions/TileMap/tilemapruntimeobject.ts | 24 +- .../tiledspriteruntimeobject-pixi-renderer.ts | 13 +- .../tiledspriteruntimeobject.ts | 29 +- .../topdownmovementruntimebehavior.ts | 21 +- .../TweenBehavior/tweenruntimebehavior.ts | 10 +- .../Video/videoruntimeobject-pixi-renderer.ts | 10 +- Extensions/Video/videoruntimeobject.ts | 21 +- .../CodeGeneration/EventsCodeGenerator.cpp | 131 +++- .../CodeGeneration/EventsCodeGenerator.h | 58 +- .../CodeGeneration/ObjectCodeGenerator.cpp | 250 ++++++ .../CodeGeneration/ObjectCodeGenerator.h | 95 +++ GDJS/GDJS/IDE/ExporterHelper.cpp | 24 + GDJS/Runtime/CustomRuntimeObject.ts | 591 ++++++++++++++ .../CustomRuntimeObjectInstanceContainer.ts | 354 +++++++++ GDJS/Runtime/RuntimeInstanceContainer.ts | 694 +++++++++++++++++ GDJS/Runtime/RuntimeSceneLayer.ts | 74 ++ GDJS/Runtime/debugger-client/hot-reloader.ts | 4 +- GDJS/Runtime/events-tools/cameratools.ts | 213 +++--- GDJS/Runtime/events-tools/inputtools.ts | 211 +++-- GDJS/Runtime/events-tools/networktools.ts | 4 +- GDJS/Runtime/events-tools/objecttools.ts | 67 +- .../Runtime/events-tools/runtimescenetools.ts | 138 ++-- GDJS/Runtime/events-tools/soundtools.ts | 144 +++- GDJS/Runtime/events-tools/storagetools.ts | 4 +- GDJS/Runtime/events-tools/stringtools.ts | 40 +- GDJS/Runtime/events-tools/variabletools.ts | 6 +- GDJS/Runtime/gd.ts | 2 +- GDJS/Runtime/layer.ts | 192 +++-- .../CustomObjectPixiRenderer.ts | 152 ++++ .../pixi-renderers/DebuggerPixiRenderer.ts | 329 ++++++++ .../RuntimeInstanceContainerPixiRenderer.ts | 30 + .../pixi-renderers/layer-pixi-renderer.ts | 17 +- .../pixi-renderers/pixi-effects-manager.ts | 6 +- .../runtimegame-pixi-renderer.ts | 18 +- .../runtimescene-pixi-renderer.ts | 273 +------ .../spriteruntimeobject-pixi-renderer.ts | 21 +- GDJS/Runtime/runtimebehavior.ts | 28 +- GDJS/Runtime/runtimegame.ts | 24 + GDJS/Runtime/runtimeobject.ts | 128 +++- GDJS/Runtime/runtimescene.ts | 708 +++-------------- GDJS/Runtime/spriteruntimeobject.ts | 88 ++- GDJS/Runtime/types/project-data.d.ts | 13 +- GDJS/tests/benchmarks/layer.js | 10 +- GDJS/tests/karma.conf.js | 4 + .../init.pixiruntimegamewithassets.js | 24 +- GDJS/tests/tests/CustomRuntimeObject.js | 722 ++++++++++++++++++ .../tests/Extensions/testruntimeobject.js | 10 +- .../Extensions/testspriteruntimeobject.js | 6 +- GDJS/tests/tests/layer.js | 4 +- GDevelop.js/Bindings/Bindings.idl | 7 + GDevelop.js/Bindings/Wrapper.cpp | 3 + GDevelop.js/scripts/generate-types.js | 6 + .../types/gdmultipleinstructionmetadata.js | 2 +- GDevelop.js/types/gdobjectcodegenerator.js | 9 + GDevelop.js/types/libgdevelop.js | 1 + newIDE/app/public/res/functions/reload.svg | 6 + .../ObjectMethodSelectorDialog.js | 19 +- .../EventsFunctionsExtensionEditor/index.js | 6 +- .../BrowserS3EventsFunctionCodeWriter.js | 13 + .../LocalEventsFunctionCodeWriter.js | 14 + .../MetadataDeclarationHelpers.js | 176 ++++- .../EventsFunctionsExtensionsLoader/index.js | 42 +- newIDE/app/src/EventsFunctionsList/index.js | 3 + .../src/UI/CustomSvgIcons/Behaviors/Reload.js | 20 + .../app/src/Utils/GDevelopServices/Asset.js | 2 +- 116 files changed, 5696 insertions(+), 1910 deletions(-) create mode 100644 GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.cpp create mode 100644 GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.h create mode 100644 GDJS/Runtime/CustomRuntimeObject.ts create mode 100644 GDJS/Runtime/CustomRuntimeObjectInstanceContainer.ts create mode 100644 GDJS/Runtime/RuntimeInstanceContainer.ts create mode 100644 GDJS/Runtime/RuntimeSceneLayer.ts create mode 100644 GDJS/Runtime/pixi-renderers/CustomObjectPixiRenderer.ts create mode 100644 GDJS/Runtime/pixi-renderers/DebuggerPixiRenderer.ts create mode 100644 GDJS/Runtime/pixi-renderers/RuntimeInstanceContainerPixiRenderer.ts create mode 100644 GDJS/tests/tests/CustomRuntimeObject.js create mode 100644 GDevelop.js/types/gdobjectcodegenerator.js create mode 100644 newIDE/app/public/res/functions/reload.svg create mode 100644 newIDE/app/src/UI/CustomSvgIcons/Behaviors/Reload.js diff --git a/Core/GDCore/IDE/ProjectStripper.cpp b/Core/GDCore/IDE/ProjectStripper.cpp index 62e09eda85..2ae6a285ee 100644 --- a/Core/GDCore/IDE/ProjectStripper.cpp +++ b/Core/GDCore/IDE/ProjectStripper.cpp @@ -5,6 +5,8 @@ */ #include "ProjectStripper.h" +#include "GDCore/Project/EventsFunctionsContainer.h" +#include "GDCore/Project/EventsFunctionsExtension.h" #include "GDCore/Project/ExternalEvents.h" #include "GDCore/Project/ExternalLayout.h" #include "GDCore/Project/Layout.h" @@ -12,7 +14,7 @@ namespace gd { -void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project& project) { +void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project &project) { project.GetObjectGroups().Clear(); while (project.GetExternalEventsCount() > 0) project.RemoveExternalEvents(project.GetExternalEvents(0).GetName()); @@ -22,7 +24,28 @@ void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project& project) { project.GetLayout(i).GetEvents().Clear(); } - project.ClearEventsFunctionsExtensions(); + // Keep the EventsBasedObject object list because it's useful for the Runtime + // to create the child-object. + for (unsigned int extensionIndex = 0; + extensionIndex < project.GetEventsFunctionsExtensionsCount(); + ++extensionIndex) { + auto &extension = project.GetEventsFunctionsExtension(extensionIndex); + auto &eventsBasedObjects = extension.GetEventsBasedObjects(); + if (eventsBasedObjects.size() == 0) { + project.RemoveEventsFunctionsExtension(extension.GetName()); + extensionIndex--; + continue; + } + for (unsigned int objectIndex = 0; objectIndex < eventsBasedObjects.size(); + ++objectIndex) { + auto &eventsBasedObject = eventsBasedObjects.at(objectIndex); + eventsBasedObject.SetFullName(""); + eventsBasedObject.SetDescription(""); + eventsBasedObject.GetEventsFunctions().GetInternalVector().clear(); + eventsBasedObject.GetPropertyDescriptors().GetInternalVector().clear(); + } + extension.GetEventsBasedBehaviors().Clear(); + } } -} // namespace gd +} // namespace gd diff --git a/Core/GDCore/Project/CustomObjectConfiguration.cpp b/Core/GDCore/Project/CustomObjectConfiguration.cpp index 652fcf2da8..80291b83b4 100644 --- a/Core/GDCore/Project/CustomObjectConfiguration.cpp +++ b/Core/GDCore/Project/CustomObjectConfiguration.cpp @@ -49,7 +49,7 @@ gd::ObjectConfiguration &CustomObjectConfiguration::GetChildObjectConfiguration( if (configurationPosition == childObjectConfigurations.end()) { childObjectConfigurations.insert(std::make_pair( objectName, - project->CreateObjectConfiguration(childObject.GetType()))); + childObject.GetConfiguration().Clone())); return *(childObjectConfigurations[objectName]); } else { diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index af7f8ec4b1..07203762e6 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -30,6 +30,8 @@ #include "GDCore/Project/Variable.h" #include "catch.hpp" +// TODO EBO Add a test where a child is removed form the EventsBasedObject +// and check the configuration still gives access to other child configuration. namespace { const gd::StandardEvent &EnsureStandardEvent(const gd::BaseEvent &baseEvent) { diff --git a/Extensions/AnchorBehavior/anchorruntimebehavior.ts b/Extensions/AnchorBehavior/anchorruntimebehavior.ts index 62f6d0fee0..08cc84060e 100644 --- a/Extensions/AnchorBehavior/anchorruntimebehavior.ts +++ b/Extensions/AnchorBehavior/anchorruntimebehavior.ts @@ -17,8 +17,12 @@ namespace gdjs { _bottomEdgeDistance: number = 0; _useLegacyBottomAndRightAnchors: boolean = false; - constructor(runtimeScene, behaviorData, owner) { - super(runtimeScene, behaviorData, owner); + constructor( + instanceContainer: gdjs.RuntimeInstanceContainer, + behaviorData, + owner: gdjs.RuntimeObject + ) { + super(instanceContainer, behaviorData, owner); this._relativeToOriginalWindowSize = !!behaviorData.relativeToOriginalWindowSize; this._leftEdgeAnchor = behaviorData.leftEdgeAnchor; this._rightEdgeAnchor = behaviorData.rightEdgeAnchor; @@ -65,11 +69,15 @@ namespace gdjs { this._invalidDistances = true; } - doStepPreEvents(runtimeScene) { - const game = runtimeScene.getGame(); + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { + const workingPoint: FloatPoint = gdjs.staticArray( + gdjs.AnchorRuntimeBehavior.prototype.doStepPreEvents + ) as FloatPoint; + // TODO EBO Make it work with event based objects or hide this behavior for them. + const game = instanceContainer.getGame(); let rendererWidth = game.getGameResolutionWidth(); let rendererHeight = game.getGameResolutionHeight(); - const layer = runtimeScene.getLayer(this.owner.getLayer()); + const layer = instanceContainer.getLayer(this.owner.getLayer()); if (this._invalidDistances) { if (this._relativeToOriginalWindowSize) { rendererWidth = game.getOriginalWidth(); @@ -79,7 +87,9 @@ namespace gdjs { //Calculate the distances from the window's bounds. const topLeftPixel = layer.convertCoords( this.owner.getDrawableX(), - this.owner.getDrawableY() + this.owner.getDrawableY(), + 0, + workingPoint ); //Left edge @@ -125,9 +135,12 @@ namespace gdjs { } } } + // It's fine to reuse workingPoint as topLeftPixel is no longer used. const bottomRightPixel = layer.convertCoords( this.owner.getDrawableX() + this.owner.getWidth(), - this.owner.getDrawableY() + this.owner.getHeight() + this.owner.getDrawableY() + this.owner.getHeight(), + 0, + workingPoint ); //Right edge @@ -268,11 +281,24 @@ namespace gdjs { } } } - const topLeftCoord = layer.convertInverseCoords(leftPixel, topPixel); + // It's fine to reuse workingPoint as topLeftPixel is no longer used. + const topLeftCoord = layer.convertInverseCoords( + leftPixel, + topPixel, + 0, + workingPoint + ); + const left = topLeftCoord[0]; + const top = topLeftCoord[1]; + const bottomRightCoord = layer.convertInverseCoords( rightPixel, - bottomPixel + bottomPixel, + 0, + workingPoint ); + const right = bottomRightCoord[0]; + const bottom = bottomRightCoord[1]; // Compatibility with GD <= 5.0.133 if (this._useLegacyBottomAndRightAnchors) { @@ -281,25 +307,25 @@ namespace gdjs { this._rightEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE ) { - this.owner.setWidth(bottomRightCoord[0] - topLeftCoord[0]); + this.owner.setWidth(right - left); } if ( this._bottomEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE ) { - this.owner.setHeight(bottomRightCoord[1] - topLeftCoord[1]); + this.owner.setHeight(bottom - top); } if ( this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE ) { this.owner.setX( - topLeftCoord[0] + this.owner.getX() - this.owner.getDrawableX() + left + this.owner.getX() - this.owner.getDrawableX() ); } if ( this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE ) { this.owner.setY( - topLeftCoord[1] + this.owner.getY() - this.owner.getDrawableY() + top + this.owner.getY() - this.owner.getDrawableY() ); } } @@ -311,15 +337,15 @@ namespace gdjs { AnchorRuntimeBehavior.HorizontalAnchor.NONE && this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE ) { - this.owner.setWidth(bottomRightCoord[0] - topLeftCoord[0]); - this.owner.setX(topLeftCoord[0]); + this.owner.setWidth(right - left); + this.owner.setX(left); } else { if ( this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE ) { this.owner.setX( - topLeftCoord[0] + this.owner.getX() - this.owner.getDrawableX() + left + this.owner.getX() - this.owner.getDrawableX() ); } if ( @@ -327,7 +353,7 @@ namespace gdjs { AnchorRuntimeBehavior.HorizontalAnchor.NONE ) { this.owner.setX( - bottomRightCoord[0] + + right + this.owner.getX() - this.owner.getDrawableX() - this.owner.getWidth() @@ -340,14 +366,14 @@ namespace gdjs { AnchorRuntimeBehavior.VerticalAnchor.NONE && this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE ) { - this.owner.setHeight(bottomRightCoord[1] - topLeftCoord[1]); - this.owner.setY(topLeftCoord[1]); + this.owner.setHeight(bottom - top); + this.owner.setY(top); } else { if ( this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE ) { this.owner.setY( - topLeftCoord[1] + this.owner.getY() - this.owner.getDrawableY() + top + this.owner.getY() - this.owner.getDrawableY() ); } if ( @@ -355,7 +381,7 @@ namespace gdjs { AnchorRuntimeBehavior.VerticalAnchor.NONE ) { this.owner.setY( - bottomRightCoord[1] + + bottom + this.owner.getY() - this.owner.getDrawableY() - this.owner.getHeight() @@ -366,7 +392,7 @@ namespace gdjs { } } - doStepPostEvents(runtimeScene) {} + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {} static HorizontalAnchor = { NONE: 0, diff --git a/Extensions/BBText/bbtextruntimeobject-pixi-renderer.ts b/Extensions/BBText/bbtextruntimeobject-pixi-renderer.ts index f4169668c0..abf94df248 100644 --- a/Extensions/BBText/bbtextruntimeobject-pixi-renderer.ts +++ b/Extensions/BBText/bbtextruntimeobject-pixi-renderer.ts @@ -10,11 +10,11 @@ namespace gdjs { /** * @param runtimeObject The object to render - * @param runtimeScene The gdjs.RuntimeScene in which the object is + * @param instanceContainer The gdjs.RuntimeInstanceContainer in which the object is */ constructor( runtimeObject: gdjs.BBTextRuntimeObject, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { this._object = runtimeObject; @@ -22,7 +22,7 @@ namespace gdjs { if (this._pixiObject === undefined) { this._pixiObject = new MultiStyleText(runtimeObject._text, { default: { - fontFamily: runtimeScene + fontFamily: instanceContainer .getGame() .getFontManager() .getFontFamily(runtimeObject._fontFamily), @@ -44,7 +44,7 @@ namespace gdjs { this.updateFontFamily(); this.updateFontSize(); } - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject(this._pixiObject, runtimeObject.getZOrder()); @@ -95,7 +95,8 @@ namespace gdjs { } updateFontFamily(): void { - this._pixiObject.textStyles.default.fontFamily = this._object._runtimeScene + this._pixiObject.textStyles.default.fontFamily = this._object + .getInstanceContainer() .getGame() .getFontManager() .getFontFamily(this._object._fontFamily); diff --git a/Extensions/BBText/bbtextruntimeobject.ts b/Extensions/BBText/bbtextruntimeobject.ts index 404917d0fe..87f3fe3191 100644 --- a/Extensions/BBText/bbtextruntimeobject.ts +++ b/Extensions/BBText/bbtextruntimeobject.ts @@ -48,11 +48,14 @@ namespace gdjs { hidden: boolean; /** - * @param runtimeScene The scene the object belongs to. + * @param instanceContainer The container the object belongs to. * @param objectData The object data used to initialize the object */ - constructor(runtimeScene: gdjs.RuntimeScene, objectData: BBTextObjectData) { - super(runtimeScene, objectData); + constructor( + instanceContainer: gdjs.RuntimeInstanceContainer, + objectData: BBTextObjectData + ) { + super(instanceContainer, objectData); // @ts-ignore - parseFloat should not be required, but GDevelop 5.0 beta 92 and below were storing it as a string. this._opacity = parseFloat(objectData.content.opacity); this._text = objectData.content.text; @@ -62,7 +65,10 @@ namespace gdjs { this._fontSize = parseFloat(objectData.content.fontSize); this._wordWrap = objectData.content.wordWrap; this._align = objectData.content.align; - this._renderer = new gdjs.BBTextRuntimeObjectRenderer(this, runtimeScene); + this._renderer = new gdjs.BBTextRuntimeObjectRenderer( + this, + instanceContainer + ); this.hidden = !objectData.content.visible; // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. @@ -122,8 +128,8 @@ namespace gdjs { } } - onDestroyFromScene(runtimeScene): void { - super.onDestroyFromScene(runtimeScene); + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { + super.onDestroyFromScene(instanceContainer); } /** @@ -239,7 +245,7 @@ namespace gdjs { this._wrappingWidth = width; this._renderer.updateWrappingWidth(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -254,7 +260,7 @@ namespace gdjs { this._wordWrap = wordWrap; this._renderer.updateWordWrap(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } getWordWrap() { diff --git a/Extensions/BitmapText/bitmaptextruntimeobject-pixi-renderer.ts b/Extensions/BitmapText/bitmaptextruntimeobject-pixi-renderer.ts index 66777b89a1..74b16df0cf 100644 --- a/Extensions/BitmapText/bitmaptextruntimeobject-pixi-renderer.ts +++ b/Extensions/BitmapText/bitmaptextruntimeobject-pixi-renderer.ts @@ -10,16 +10,16 @@ namespace gdjs { /** * @param runtimeObject The object to render - * @param runtimeScene The gdjs.RuntimeScene in which the object is + * @param instanceContainer The container in which the object is */ constructor( runtimeObject: gdjs.BitmapTextRuntimeObject, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { this._object = runtimeObject; // Obtain the bitmap font to use in the object. - const bitmapFont = runtimeScene + const bitmapFont = instanceContainer .getGame() .getBitmapFontManager() .obtainBitmapFont( @@ -32,7 +32,7 @@ namespace gdjs { }); // Set the object on the scene - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject(this._pixiObject, runtimeObject.getZOrder()); @@ -59,7 +59,8 @@ namespace gdjs { onDestroy() { // Mark the font from the object as not used anymore. - this._object._runtimeScene + this._object + .getInstanceContainer() .getGame() .getBitmapFontManager() .releaseBitmapFont(this._pixiObject.fontName); @@ -73,7 +74,8 @@ namespace gdjs { updateFont(): void { // Get the new bitmap font to use - const bitmapFont = this._object._runtimeScene + const bitmapFont = this._object + .getInstanceContainer() .getGame() .getBitmapFontManager() .obtainBitmapFont( @@ -82,7 +84,8 @@ namespace gdjs { ); // Mark the old font as not used anymore - this._object._runtimeScene + this._object + .getInstanceContainer() .getGame() .getBitmapFontManager() .releaseBitmapFont(this._pixiObject.fontName); diff --git a/Extensions/BitmapText/bitmaptextruntimeobject.ts b/Extensions/BitmapText/bitmaptextruntimeobject.ts index 2e500ed255..76f6ba56f3 100644 --- a/Extensions/BitmapText/bitmaptextruntimeobject.ts +++ b/Extensions/BitmapText/bitmaptextruntimeobject.ts @@ -50,14 +50,14 @@ namespace gdjs { _renderer: gdjs.BitmapTextRuntimeObjectPixiRenderer; /** - * @param runtimeScene The scene the object belongs to. + * @param instanceContainer The container the object belongs to. * @param objectData The object data used to initialize the object */ constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, objectData: BitmapTextObjectData ) { - super(runtimeScene, objectData); + super(instanceContainer, objectData); this._opacity = objectData.content.opacity; this._text = objectData.content.text; @@ -73,7 +73,7 @@ namespace gdjs { this._renderer = new gdjs.BitmapTextRuntimeObjectRenderer( this, - runtimeScene + instanceContainer ); // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. @@ -137,8 +137,8 @@ namespace gdjs { } } - onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void { - super.onDestroyFromScene(runtimeScene); + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { + super.onDestroyFromScene(instanceContainer); this._renderer.onDestroy(); } @@ -148,7 +148,7 @@ namespace gdjs { setText(text: string): void { this._text = text; this._renderer.updateTextContent(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -170,7 +170,7 @@ namespace gdjs { setScale(scale: float): void { this._scale = scale; this._renderer.updateScale(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } getScale(): float { @@ -276,7 +276,7 @@ namespace gdjs { setWrappingWidth(width: float): void { this._wrappingWidth = width; this._renderer.updateWrappingWidth(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -289,7 +289,7 @@ namespace gdjs { setWordWrap(wordWrap: boolean): void { this._wordWrap = wordWrap; this._renderer.updateWrappingWidth(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } getWordWrap(): boolean { diff --git a/Extensions/DebuggerTools/debuggertools.ts b/Extensions/DebuggerTools/debuggertools.ts index 0280064ea1..e212d97b29 100644 --- a/Extensions/DebuggerTools/debuggertools.ts +++ b/Extensions/DebuggerTools/debuggertools.ts @@ -7,10 +7,12 @@ namespace gdjs { export namespace debuggerTools { /** * Stop the game execution. - * @param runtimeScene - The current scene. + * @param instanceContainer - The current container. */ - export const pause = function (runtimeScene: gdjs.RuntimeScene) { - runtimeScene.getGame().pause(true); + export const pause = function ( + instanceContainer: gdjs.RuntimeInstanceContainer + ) { + instanceContainer.getGame().pause(true); }; /** @@ -29,20 +31,20 @@ namespace gdjs { /** * Enable or disable the debug draw. - * @param runtimeScene - The current scene. + * @param instanceContainer - The current container. * @param enableDebugDraw - true to enable the debug draw, false to disable it. * @param showHiddenInstances - true to apply the debug draw to hidden objects. * @param showPointsNames - true to show point names. * @param showCustomPoints - true to show custom points of Sprite objects. */ export const enableDebugDraw = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, enableDebugDraw: boolean, showHiddenInstances: boolean, showPointsNames: boolean, showCustomPoints: boolean ) { - runtimeScene.enableDebugDraw( + instanceContainer.enableDebugDraw( enableDebugDraw, showHiddenInstances, showPointsNames, diff --git a/Extensions/DestroyOutsideBehavior/destroyoutsideruntimebehavior.ts b/Extensions/DestroyOutsideBehavior/destroyoutsideruntimebehavior.ts index 95da2c585a..a3be3b1e25 100644 --- a/Extensions/DestroyOutsideBehavior/destroyoutsideruntimebehavior.ts +++ b/Extensions/DestroyOutsideBehavior/destroyoutsideruntimebehavior.ts @@ -11,8 +11,12 @@ namespace gdjs { export class DestroyOutsideRuntimeBehavior extends gdjs.RuntimeBehavior { _extraBorder: any; - constructor(runtimeScene, behaviorData, owner) { - super(runtimeScene, behaviorData, owner); + constructor( + instanceContainer: gdjs.RuntimeInstanceContainer, + behaviorData, + owner + ) { + super(instanceContainer, behaviorData, owner); this._extraBorder = behaviorData.extraBorder || 0; } @@ -23,14 +27,14 @@ namespace gdjs { return true; } - doStepPostEvents(runtimeScene) { + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { // TODO: This would better be done using the object AABB (getAABB), as (`getCenterX`;`getCenterY`) point // is not necessarily in the middle of the object (for sprites for example). const ow = this.owner.getWidth(); const oh = this.owner.getHeight(); const ocx = this.owner.getDrawableX() + this.owner.getCenterX(); const ocy = this.owner.getDrawableY() + this.owner.getCenterY(); - const layer = runtimeScene.getLayer(this.owner.getLayer()); + const layer = instanceContainer.getLayer(this.owner.getLayer()); const boundingCircleRadius = Math.sqrt(ow * ow + oh * oh) / 2.0; if ( ocx + boundingCircleRadius + this._extraBorder < @@ -43,7 +47,7 @@ namespace gdjs { layer.getCameraY() + layer.getCameraHeight() / 2 ) { //We are outside the camera area. - this.owner.deleteFromScene(runtimeScene); + this.owner.deleteFromScene(instanceContainer); } } diff --git a/Extensions/DialogueTree/dialoguetools.ts b/Extensions/DialogueTree/dialoguetools.ts index bd224d1c9a..7f2afdb97a 100644 --- a/Extensions/DialogueTree/dialoguetools.ts +++ b/Extensions/DialogueTree/dialoguetools.ts @@ -30,16 +30,16 @@ namespace gdjs { /** * Load the Dialogue Tree data from a JSON resource. * - * @param runtimeScene The scene where the dialogue is running. + * @param instanceContainer The scene where the dialogue is running. * @param jsonResourceName The JSON resource where to load the Dialogue Tree data from. The data is a JSON string usually created with [Yarn Dialogue Editor](https://github.com/InfiniteAmmoInc/Yarn). * @param startDialogueNode The Dialogue Branch to start the Dialogue Tree from. If left empty, the data will only be loaded, but can later be initialized via another action */ gdjs.dialogueTree.loadFromJsonFile = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, jsonResourceName: string, startDialogueNode: string ) { - runtimeScene + instanceContainer .getGame() .getJsonManager() .loadJson(jsonResourceName, function (error, content) { diff --git a/Extensions/DraggableBehavior/draggableruntimebehavior.ts b/Extensions/DraggableBehavior/draggableruntimebehavior.ts index e685ed31b3..bbd2754eb5 100644 --- a/Extensions/DraggableBehavior/draggableruntimebehavior.ts +++ b/Extensions/DraggableBehavior/draggableruntimebehavior.ts @@ -16,8 +16,12 @@ namespace gdjs { _draggedByDraggableManager: DraggableManager | null = null; _checkCollisionMask: boolean; - constructor(runtimeScene, behaviorData, owner) { - super(runtimeScene, behaviorData, owner); + constructor( + instanceContainer: gdjs.RuntimeInstanceContainer, + behaviorData, + owner + ) { + super(instanceContainer, behaviorData, owner); this._checkCollisionMask = behaviorData.checkCollisionMask ? true : false; } @@ -45,21 +49,21 @@ namespace gdjs { this._draggedByDraggableManager = null; } - _tryBeginDrag(runtimeScene) { + _tryBeginDrag(instanceContainer: gdjs.RuntimeInstanceContainer) { if (this._draggedByDraggableManager) { return false; } - const inputManager = runtimeScene.getGame().getInputManager(); + const inputManager = instanceContainer.getGame().getInputManager(); //Try mouse const mouseDraggableManager = DraggableManager.getMouseManager( - runtimeScene + instanceContainer ); if ( inputManager.isMouseButtonPressed(0) && !mouseDraggableManager.isDragging(this) ) { - if (mouseDraggableManager.tryAndTakeDragging(runtimeScene, this)) { + if (mouseDraggableManager.tryAndTakeDragging(instanceContainer, this)) { this._draggedByDraggableManager = mouseDraggableManager; return true; } @@ -68,13 +72,15 @@ namespace gdjs { const touchIds = inputManager.getStartedTouchIdentifiers(); for (let i = 0; i < touchIds.length; ++i) { const touchDraggableManager = DraggableManager.getTouchManager( - runtimeScene, + instanceContainer, touchIds[i] ); if (touchDraggableManager.isDragging(this)) { continue; } - if (touchDraggableManager.tryAndTakeDragging(runtimeScene, this)) { + if ( + touchDraggableManager.tryAndTakeDragging(instanceContainer, this) + ) { this._draggedByDraggableManager = touchDraggableManager; return true; } @@ -83,40 +89,46 @@ namespace gdjs { return false; } - _shouldEndDrag(runtimeScene) { + _shouldEndDrag(instanceContainer: gdjs.RuntimeInstanceContainer) { if (!this._draggedByDraggableManager) { return false; } - return this._draggedByDraggableManager.shouldEndDrag(runtimeScene, this); + return this._draggedByDraggableManager.shouldEndDrag( + instanceContainer, + this + ); } - _updateObjectPosition(runtimeScene) { + _updateObjectPosition(instanceContainer: gdjs.RuntimeInstanceContainer) { if (!this._draggedByDraggableManager) { return false; } - this._draggedByDraggableManager.updateObjectPosition(runtimeScene, this); + this._draggedByDraggableManager.updateObjectPosition( + instanceContainer, + this + ); return true; } - doStepPreEvents(runtimeScene) { - this._tryBeginDrag(runtimeScene); - if (this._shouldEndDrag(runtimeScene)) { + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { + this._tryBeginDrag(instanceContainer); + if (this._shouldEndDrag(instanceContainer)) { this._endDrag(); } - this._updateObjectPosition(runtimeScene); + this._updateObjectPosition(instanceContainer); } - doStepPostEvents(runtimeScene) { + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { const mouseDraggableManager = DraggableManager.getMouseManager( - runtimeScene + instanceContainer ); - mouseDraggableManager.leftPressedLastFrame = runtimeScene + mouseDraggableManager.leftPressedLastFrame = instanceContainer .getGame() .getInputManager() .isMouseButtonPressed(0); } - isDragged(runtimeScene): boolean { + isDragged(instanceContainer: gdjs.RuntimeInstanceContainer): boolean { return !!this._draggedByDraggableManager; } } @@ -137,53 +149,53 @@ namespace gdjs { protected _xOffset: number = 0; protected _yOffset: number = 0; - constructor(runtimeScene: gdjs.RuntimeScene) {} + constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {} /** - * Get the platforms manager of a scene. + * Get the platforms manager of an instance container. */ static getMouseManager( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): MouseDraggableManager { // @ts-ignore - if (!runtimeScene.mouseDraggableManager) { + if (!instanceContainer.mouseDraggableManager) { //Create the shared manager if necessary. // @ts-ignore - runtimeScene.mouseDraggableManager = new MouseDraggableManager( - runtimeScene + instanceContainer.mouseDraggableManager = new MouseDraggableManager( + instanceContainer ); } // @ts-ignore - return runtimeScene.mouseDraggableManager; + return instanceContainer.mouseDraggableManager; } /** - * Get the platforms manager of a scene. + * Get the platforms manager of an instance container. */ static getTouchManager( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, touchId: integer ): DraggableManager { // @ts-ignore - if (!runtimeScene.touchDraggableManagers) { + if (!instanceContainer.touchDraggableManagers) { //Create the shared manager if necessary. // @ts-ignore - runtimeScene.touchDraggableManagers = []; + instanceContainer.touchDraggableManagers = []; } // @ts-ignore - if (!runtimeScene.touchDraggableManagers[touchId]) { + if (!instanceContainer.touchDraggableManagers[touchId]) { //Create the shared manager if necessary. // @ts-ignore - runtimeScene.touchDraggableManagers[ + instanceContainer.touchDraggableManagers[ touchId - ] = new TouchDraggableManager(runtimeScene, touchId); + ] = new TouchDraggableManager(instanceContainer, touchId); } // @ts-ignore - return runtimeScene.touchDraggableManagers[touchId]; + return instanceContainer.touchDraggableManagers[touchId]; } tryAndTakeDragging( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, draggableRuntimeBehavior: DraggableRuntimeBehavior ) { if ( @@ -193,7 +205,10 @@ namespace gdjs { ) { return false; } - const position = this.getPosition(runtimeScene, draggableRuntimeBehavior); + const position = this.getPosition( + instanceContainer, + draggableRuntimeBehavior + ); if ( !draggableRuntimeBehavior.owner.insideObject(position[0], position[1]) ) { @@ -218,10 +233,13 @@ namespace gdjs { } updateObjectPosition( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, draggableRuntimeBehavior: DraggableRuntimeBehavior ) { - const position = this.getPosition(runtimeScene, draggableRuntimeBehavior); + const position = this.getPosition( + instanceContainer, + draggableRuntimeBehavior + ); if ( draggableRuntimeBehavior.owner.getX() != position[0] - this._xOffset || draggableRuntimeBehavior.owner.getY() != position[1] - this._yOffset @@ -241,11 +259,11 @@ namespace gdjs { draggableRuntimeBehavior: DraggableRuntimeBehavior ): boolean; abstract shouldEndDrag( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, draggableRuntimeBehavior: DraggableRuntimeBehavior ): boolean; abstract getPosition( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, draggableRuntimeBehavior: DraggableRuntimeBehavior ): FloatPoint; } @@ -257,8 +275,8 @@ namespace gdjs { /** Used to only start dragging when clicking. */ leftPressedLastFrame = false; - constructor(runtimeScene: gdjs.RuntimeScene) { - super(runtimeScene); + constructor(instanceContainer: gdjs.RuntimeInstanceContainer) { + super(instanceContainer); } isDragging(draggableRuntimeBehavior: DraggableRuntimeBehavior): boolean { @@ -266,20 +284,28 @@ namespace gdjs { } getPosition( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, draggableRuntimeBehavior: DraggableRuntimeBehavior ): FloatPoint { - const inputManager = runtimeScene.getGame().getInputManager(); - return runtimeScene + const workingPoint: FloatPoint = gdjs.staticArray( + MouseDraggableManager.prototype.getPosition + ) as FloatPoint; + const inputManager = instanceContainer.getGame().getInputManager(); + return instanceContainer .getLayer(draggableRuntimeBehavior.owner.getLayer()) - .convertCoords(inputManager.getMouseX(), inputManager.getMouseY()); + .convertCoords( + inputManager.getMouseX(), + inputManager.getMouseY(), + 0, + workingPoint + ); } shouldEndDrag( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, draggableRuntimeBehavior: DraggableRuntimeBehavior ): boolean { - const inputManager = runtimeScene.getGame().getInputManager(); + const inputManager = instanceContainer.getGame().getInputManager(); return !inputManager.isMouseButtonPressed(0); } } @@ -290,8 +316,11 @@ namespace gdjs { class TouchDraggableManager extends DraggableManager { private _touchId: integer; - constructor(runtimeScene: gdjs.RuntimeScene, touchId: integer) { - super(runtimeScene); + constructor( + instanceContainer: gdjs.RuntimeInstanceContainer, + touchId: integer + ) { + super(instanceContainer); this._touchId = touchId; } @@ -300,23 +329,28 @@ namespace gdjs { } getPosition( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, draggableRuntimeBehavior: DraggableRuntimeBehavior ): FloatPoint { - const inputManager = runtimeScene.getGame().getInputManager(); - return runtimeScene + const workingPoint: FloatPoint = gdjs.staticArray( + TouchDraggableManager.prototype.getPosition + ) as FloatPoint; + const inputManager = instanceContainer.getGame().getInputManager(); + return instanceContainer .getLayer(draggableRuntimeBehavior.owner.getLayer()) .convertCoords( inputManager.getTouchX(this._touchId), - inputManager.getTouchY(this._touchId) + inputManager.getTouchY(this._touchId), + 0, + workingPoint ); } shouldEndDrag( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, draggableRuntimeBehavior: DraggableRuntimeBehavior ): boolean { - const inputManager = runtimeScene.getGame().getInputManager(); + const inputManager = instanceContainer.getGame().getInputManager(); return ( inputManager.getAllTouchIdentifiers().indexOf(this._touchId) === -1 ); diff --git a/Extensions/ExampleJsExtension/dummyruntimebehavior.ts b/Extensions/ExampleJsExtension/dummyruntimebehavior.ts index 028abc4953..3b6aad8c7f 100644 --- a/Extensions/ExampleJsExtension/dummyruntimebehavior.ts +++ b/Extensions/ExampleJsExtension/dummyruntimebehavior.ts @@ -11,11 +11,11 @@ namespace gdjs { _textToSet: string; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData: any, owner: gdjs.RuntimeObject ) { - super(runtimeScene, behaviorData, owner); + super(instanceContainer, behaviorData, owner); // Here you can access to the behavior data (JSON declared in JsExtension.js) // using behaviorData: @@ -37,7 +37,7 @@ namespace gdjs { onDeActivate() {} - doStepPreEvents(runtimeScene) { + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { // This is run at every frame, before events are launched. this.owner .getVariables() @@ -45,7 +45,7 @@ namespace gdjs { .setString(this._textToSet); } - doStepPostEvents(runtimeScene) { + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { // This is run at every frame, after events are launched. } } diff --git a/Extensions/ExampleJsExtension/dummyruntimeobject-pixi-renderer.ts b/Extensions/ExampleJsExtension/dummyruntimeobject-pixi-renderer.ts index dae2c909ab..490a7cc719 100644 --- a/Extensions/ExampleJsExtension/dummyruntimeobject-pixi-renderer.ts +++ b/Extensions/ExampleJsExtension/dummyruntimeobject-pixi-renderer.ts @@ -11,11 +11,11 @@ namespace gdjs { /** * @param runtimeObject The object to render - * @param runtimeScene The gdjs.RuntimeScene in which the object is + * @param instanceContainer The gdjs.RuntimeScene in which the object is */ constructor( runtimeObject: gdjs.DummyRuntimeObject, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { this._object = runtimeObject; @@ -27,12 +27,12 @@ namespace gdjs { } // You can also create a PIXI sprite or other PIXI object - // this._imageManager = runtimeScene.getGame().getImageManager(); + // this._imageManager = instanceContainer.getGame().getImageManager(); // if ( this._sprite === undefined ) // this._sprite = new PIXI.Sprite(this._imageManager.getInvalidPIXITexture()); this._text.anchor.x = 0.5; this._text.anchor.y = 0.5; - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject(this._text, runtimeObject.getZOrder()); diff --git a/Extensions/ExampleJsExtension/dummyruntimeobject.ts b/Extensions/ExampleJsExtension/dummyruntimeobject.ts index 7636b79e46..02e090b42c 100644 --- a/Extensions/ExampleJsExtension/dummyruntimeobject.ts +++ b/Extensions/ExampleJsExtension/dummyruntimeobject.ts @@ -14,11 +14,14 @@ namespace gdjs { // @ts-expect-error ts-migrate(2564) FIXME: Property 'opacity' has no initializer and is not d... Remove this comment to see the full error message opacity: float; - constructor(runtimeScene, objectData) { + constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) { // *ALWAYS* call the base gdjs.RuntimeObject constructor. - super(runtimeScene, objectData); + super(instanceContainer, objectData); this._property1 = objectData.content.property1; - this._renderer = new gdjs.DummyRuntimeObjectRenderer(this, runtimeScene); + this._renderer = new gdjs.DummyRuntimeObjectRenderer( + this, + instanceContainer + ); // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. this.onCreated(); @@ -40,9 +43,9 @@ namespace gdjs { /** * Called once during the game loop, before events and rendering. - * @param runtimeScene The gdjs.RuntimeScene the object belongs to. + * @param instanceContainer The gdjs.RuntimeScene the object belongs to. */ - update(runtimeScene: gdjs.RuntimeScene): void { + update(instanceContainer: gdjs.RuntimeInstanceContainer): void { // This is an example: typically you want to make sure the renderer // is up to date with the object. this._renderer.ensureUpToDate(); diff --git a/Extensions/ExampleJsExtension/dummywithshareddataruntimebehavior.ts b/Extensions/ExampleJsExtension/dummywithshareddataruntimebehavior.ts index 5321e2f918..a4d511d4cd 100644 --- a/Extensions/ExampleJsExtension/dummywithshareddataruntimebehavior.ts +++ b/Extensions/ExampleJsExtension/dummywithshareddataruntimebehavior.ts @@ -4,20 +4,20 @@ namespace gdjs { _textToSet: string; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData: any, owner: gdjs.RuntimeObject ) { - super(runtimeScene, behaviorData, owner); + super(instanceContainer, behaviorData, owner); // Here you can access to the behavior data (JSON declared in JsExtension.js) // using behaviorData: this._textToSet = behaviorData.property1; // You can also access to the shared data: - const sharedData = runtimeScene.getInitialSharedDataForBehavior( - behaviorData.name - ); + const sharedData = instanceContainer + .getScene() + .getInitialSharedDataForBehavior(behaviorData.name); this._textToSet = (sharedData as any).sharedProperty1; // You can also run arbitrary code at the creation of the behavior: @@ -40,7 +40,7 @@ namespace gdjs { onDeActivate() {} - doStepPreEvents(runtimeScene) { + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { // This is run at every frame, before events are launched. this.owner .getVariables() @@ -48,7 +48,7 @@ namespace gdjs { .setString(this._textToSet); } - doStepPostEvents(runtimeScene) { + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { // This is run at every frame, after events are launched. } } diff --git a/Extensions/ExampleJsExtension/examplejsextensiontools.ts b/Extensions/ExampleJsExtension/examplejsextensiontools.ts index ec27f96c7b..d30af7068f 100644 --- a/Extensions/ExampleJsExtension/examplejsextensiontools.ts +++ b/Extensions/ExampleJsExtension/examplejsextensiontools.ts @@ -25,7 +25,9 @@ namespace gdjs { * In **rare cases** you may want to run code at the start of the scene. You can define a callback * that will be called at this moment. */ - gdjs.registerRuntimeSceneLoadedCallback(function (runtimeScene) { + gdjs.registerRuntimeSceneLoadedCallback(function ( + runtimeScene: gdjs.RuntimeScene + ) { logger.log('A gdjs.RuntimeScene was loaded:', runtimeScene); }); @@ -33,7 +35,9 @@ namespace gdjs { * In **rare cases** you may want to run code at the end of a scene. You can define a callback * that will be called at this moment. */ - gdjs.registerRuntimeSceneUnloadedCallback(function (runtimeScene) { + gdjs.registerRuntimeSceneUnloadedCallback(function ( + runtimeScene: gdjs.RuntimeScene + ) { logger.log('A gdjs.RuntimeScene was unloaded:', runtimeScene); }); @@ -41,12 +45,12 @@ namespace gdjs { * In **very rare cases** you may want to run code whenever an object is deleted. */ gdjs.registerObjectDeletedFromSceneCallback(function ( - runtimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, runtimeObject ) { logger.log( 'A gdjs.RuntimeObject was deleted from a gdjs.RuntimeScene:', - runtimeScene, + instanceContainer, runtimeObject ); }); diff --git a/Extensions/FileSystem/filesystemtools.ts b/Extensions/FileSystem/filesystemtools.ts index 910b7f0ff7..dabd149dd6 100644 --- a/Extensions/FileSystem/filesystemtools.ts +++ b/Extensions/FileSystem/filesystemtools.ts @@ -48,13 +48,16 @@ namespace gdjs { /** * Get the path to 'Desktop' folder. - * @param runtimeScene The current scene + * @param instanceContainer The current container * @return The path to the desktop folder */ export const getDesktopPath = function ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): string { - const remote = runtimeScene.getGame().getRenderer().getElectronRemote(); + const remote = instanceContainer + .getGame() + .getRenderer() + .getElectronRemote(); const app = remote ? remote.app : null; if (app) { return app.getPath('desktop') || ''; @@ -65,13 +68,16 @@ namespace gdjs { /** * Get the path to 'Documents' folder. - * @param runtimeScene The current scene + * @param instanceContainer The current container * @return The path to the documents folder */ export const getDocumentsPath = function ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): string { - const remote = runtimeScene.getGame().getRenderer().getElectronRemote(); + const remote = instanceContainer + .getGame() + .getRenderer() + .getElectronRemote(); const app = remote ? remote.app : null; if (app) { return app.getPath('documents') || ''; @@ -82,13 +88,16 @@ namespace gdjs { /** * Get the path to 'Pictures' folder. - * @param runtimeScene The current scene + * @param instanceContainer The current container * @return The path to the pictures folder */ export const getPicturesPath = function ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): string { - const remote = runtimeScene.getGame().getRenderer().getElectronRemote(); + const remote = instanceContainer + .getGame() + .getRenderer() + .getElectronRemote(); const app = remote ? remote.app : null; if (app) { return app.getPath('pictures') || ''; @@ -99,13 +108,16 @@ namespace gdjs { /** * Get the path to this application 'Executable' file. - * @param runtimeScene The current scene + * @param instanceContainer The current container * @return The path to this applications executable file */ export const getExecutablePath = function ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): string { - const remote = runtimeScene.getGame().getRenderer().getElectronRemote(); + const remote = instanceContainer + .getGame() + .getRenderer() + .getElectronRemote(); const app = remote ? remote.app : null; if (app) { return app.getPath('exe') || ''; @@ -116,14 +128,16 @@ namespace gdjs { /** * Get the path to this application 'Executable' folder. - * @param runtimeScene The current scene + * @param instanceContainer The current container * @return The path to this applications executable folder */ export const getExecutableFolderPath = function ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): string { const path = gdjs.fileSystem._getPath(); - const executablePath = gdjs.fileSystem.getExecutablePath(runtimeScene); + const executablePath = gdjs.fileSystem.getExecutablePath( + instanceContainer + ); if (!path) { return ''; } @@ -132,13 +146,16 @@ namespace gdjs { /** * Get the path to 'UserData' folder. - * @param runtimeScene The current scene + * @param instanceContainer The current container * @return The path to userdata folder */ export const getUserdataPath = function ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): string { - const remote = runtimeScene.getGame().getRenderer().getElectronRemote(); + const remote = instanceContainer + .getGame() + .getRenderer() + .getElectronRemote(); const app = remote ? remote.app : null; if (app) { return app.getPath('userData') || ''; @@ -152,9 +169,12 @@ namespace gdjs { * @return The path to user's "home" folder */ export const getUserHomePath = function ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): string { - const remote = runtimeScene.getGame().getRenderer().getElectronRemote(); + const remote = instanceContainer + .getGame() + .getRenderer() + .getElectronRemote(); const app = remote ? remote.app : null; if (app) { return app.getPath('home') || ''; @@ -165,13 +185,16 @@ namespace gdjs { /** * Get the path to 'Temp' folder. - * @param runtimeScene The current scene + * @param instanceContainer The current container * @return The path to temp folder */ export const getTempPath = function ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): string { - const remote = runtimeScene.getGame().getRenderer().getElectronRemote(); + const remote = instanceContainer + .getGame() + .getRenderer() + .getElectronRemote(); const app = remote ? remote.app : null; if (app) { return app.getPath('temp') || ''; diff --git a/Extensions/Inventory/inventorytools.ts b/Extensions/Inventory/inventorytools.ts index f07a15e966..e6178c0d93 100644 --- a/Extensions/Inventory/inventorytools.ts +++ b/Extensions/Inventory/inventorytools.ts @@ -1,7 +1,13 @@ namespace gdjs { + export interface RuntimeGame { + inventories: { [name: string]: gdjs.Inventory }; + } export class InventoryManager { - static get(runtimeScene, name): gdjs.Inventory { - const game = runtimeScene.getGame(); + static get( + instanceContainer: gdjs.RuntimeInstanceContainer, + name: string + ): gdjs.Inventory { + const game = instanceContainer.getGame(); if (!game.inventories) { game.inventories = {}; } @@ -15,70 +21,106 @@ namespace gdjs { export namespace evtTools { export namespace inventory { - export const add = function (runtimeScene, inventoryName, name) { - return InventoryManager.get(runtimeScene, inventoryName).add(name); + export const add = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + inventoryName: string, + name: string + ) { + return InventoryManager.get(instanceContainer, inventoryName).add(name); }; - export const remove = function (runtimeScene, inventoryName, name) { - return InventoryManager.get(runtimeScene, inventoryName).remove(name); + export const remove = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + inventoryName: string, + name: string + ) { + return InventoryManager.get(instanceContainer, inventoryName).remove( + name + ); }; - export const count = function (runtimeScene, inventoryName, name) { - return InventoryManager.get(runtimeScene, inventoryName).count(name); + export const count = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + inventoryName: string, + name: string + ) { + return InventoryManager.get(instanceContainer, inventoryName).count( + name + ); }; - export const has = function (runtimeScene, inventoryName, name) { - return InventoryManager.get(runtimeScene, inventoryName).has(name); + export const has = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + inventoryName: string, + name: string + ) { + return InventoryManager.get(instanceContainer, inventoryName).has(name); }; export const setMaximum = function ( - runtimeScene, - inventoryName, - name, - maxCount + instanceContainer: gdjs.RuntimeInstanceContainer, + inventoryName: string, + name: string, + maxCount: number ) { - return InventoryManager.get(runtimeScene, inventoryName).setMaximum( - name, - maxCount - ); + return InventoryManager.get( + instanceContainer, + inventoryName + ).setMaximum(name, maxCount); }; export const setUnlimited = function ( - runtimeScene, - inventoryName, - name, - enable + instanceContainer: gdjs.RuntimeInstanceContainer, + inventoryName: string, + name: string, + enable: boolean ) { - return InventoryManager.get(runtimeScene, inventoryName).setUnlimited( - name, - enable + return InventoryManager.get( + instanceContainer, + inventoryName + ).setUnlimited(name, enable); + }; + + export const isFull = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + inventoryName: string, + name: string + ) { + return InventoryManager.get(instanceContainer, inventoryName).isFull( + name ); }; - export const isFull = function (runtimeScene, inventoryName, name) { - return InventoryManager.get(runtimeScene, inventoryName).isFull(name); - }; - - export const equip = function (runtimeScene, inventoryName, name, equip) { - return InventoryManager.get(runtimeScene, inventoryName).equip( + export const equip = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + inventoryName: string, + name: string, + equip: boolean + ) { + return InventoryManager.get(instanceContainer, inventoryName).equip( name, equip ); }; - export const isEquipped = function (runtimeScene, inventoryName, name) { - return InventoryManager.get(runtimeScene, inventoryName).isEquipped( - name - ); + export const isEquipped = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + inventoryName: string, + name: string + ) { + return InventoryManager.get( + instanceContainer, + inventoryName + ).isEquipped(name); }; export const serializeToVariable = function ( - runtimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, inventoryName: string, variable: gdjs.Variable ) { const allItems = gdjs.InventoryManager.get( - runtimeScene, + instanceContainer, inventoryName ).getAllItems(); for (const name in allItems) { @@ -92,12 +134,12 @@ namespace gdjs { }; export const unserializeFromVariable = function ( - runtimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, inventoryName: string, variable: gdjs.Variable ) { const inventory = gdjs.InventoryManager.get( - runtimeScene, + instanceContainer, inventoryName ); inventory.clear(); diff --git a/Extensions/Leaderboards/leaderboardstools.ts b/Extensions/Leaderboards/leaderboardstools.ts index ee8589b520..b9a17f0df1 100644 --- a/Extensions/Leaderboards/leaderboardstools.ts +++ b/Extensions/Leaderboards/leaderboardstools.ts @@ -1,5 +1,6 @@ /// +// TODO EBO Replace runtimeScene to instanceContainer. namespace gdjs { const logger = new gdjs.Logger('Leaderboards'); export namespace evtTools { diff --git a/Extensions/Lighting/lightobstacleruntimebehavior.ts b/Extensions/Lighting/lightobstacleruntimebehavior.ts index bd083f7987..1568f4b2a5 100644 --- a/Extensions/Lighting/lightobstacleruntimebehavior.ts +++ b/Extensions/Lighting/lightobstacleruntimebehavior.ts @@ -4,26 +4,26 @@ namespace gdjs { export class LightObstaclesManager { _obstacleRBush: any; - constructor(runtimeScene: gdjs.RuntimeScene) { + constructor(instanceContainer: gdjs.RuntimeInstanceContainer) { this._obstacleRBush = new rbush(); } /** - * Get the light obstacles manager of a scene. + * Get the light obstacles manager of an instance container. */ static getManager( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): gdjs.LightObstaclesManager { // @ts-ignore - if (!runtimeScene._lightObstaclesManager) { + if (!instanceContainer._lightObstaclesManager) { // Create the shared manager if necessary. // @ts-ignore - runtimeScene._lightObstaclesManager = new gdjs.LightObstaclesManager( - runtimeScene + instanceContainer._lightObstaclesManager = new gdjs.LightObstaclesManager( + instanceContainer ); } // @ts-ignore - return runtimeScene._lightObstaclesManager; + return instanceContainer._lightObstaclesManager; } /** @@ -92,15 +92,15 @@ namespace gdjs { _registeredInManager: boolean = false; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData, owner: gdjs.RuntimeObject ) { - super(runtimeScene, behaviorData, owner); - this._manager = LightObstaclesManager.getManager(runtimeScene); + super(instanceContainer, behaviorData, owner); + this._manager = LightObstaclesManager.getManager(instanceContainer); } - doStepPreEvents(runtimeScene: gdjs.RuntimeScene) { + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { // Make sure the obstacle is or is not in the obstacles manager. if (!this.activated() && this._registeredInManager) { this._manager.removeObstacle(this); diff --git a/Extensions/Lighting/lightruntimeobject-pixi-renderer.ts b/Extensions/Lighting/lightruntimeobject-pixi-renderer.ts index 1aff06273e..d3227ee6e4 100644 --- a/Extensions/Lighting/lightruntimeobject-pixi-renderer.ts +++ b/Extensions/Lighting/lightruntimeobject-pixi-renderer.ts @@ -7,7 +7,7 @@ namespace gdjs { */ export class LightRuntimeObjectPixiRenderer { _object: gdjs.LightRuntimeObject; - _runtimeScene: gdjs.RuntimeScene; + _instanceContainer: gdjs.RuntimeInstanceContainer; _manager: gdjs.LightObstaclesManager; _radius: number; _color: [number, number, number]; @@ -30,10 +30,10 @@ namespace gdjs { constructor( runtimeObject: gdjs.LightRuntimeObject, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { this._object = runtimeObject; - this._runtimeScene = runtimeScene; + this._instanceContainer = instanceContainer; this._manager = runtimeObject.getObstaclesManager(); this._radius = runtimeObject.getRadius(); const objectColor = runtimeObject._color; @@ -57,14 +57,14 @@ namespace gdjs { ]); this._indexBuffer = new Uint16Array([0, 1, 2, 0, 2, 3]); this.updateMesh(); - this._isPreview = runtimeScene.getGame().isPreview(); + this._isPreview = instanceContainer.getGame().isPreview(); this._lightBoundingPoly = gdjs.Polygon.createRectangle(0, 0); this.updateDebugMode(); // Objects will be added in lighting layer, this is just to maintain consistency. if (this._light) { - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject( @@ -198,7 +198,7 @@ namespace gdjs { const texture = this._object.getTexture(); this._texture = texture !== '' - ? (this._runtimeScene + ? (this._instanceContainer .getGame() .getImageManager() as gdjs.PixiImageManager).getPIXITexture( texture diff --git a/Extensions/Lighting/lightruntimeobject.ts b/Extensions/Lighting/lightruntimeobject.ts index 002aaa7a1c..7ac645bd0e 100644 --- a/Extensions/Lighting/lightruntimeobject.ts +++ b/Extensions/Lighting/lightruntimeobject.ts @@ -27,7 +27,7 @@ namespace gdjs { _texture: string; _obstaclesManager: gdjs.LightObstaclesManager; _renderer: gdjs.LightRuntimeObjectRenderer; - _runtimeScene: gdjs.RuntimeScene; + _instanceContainer: gdjs.RuntimeScene; constructor( runtimeScene: gdjs.RuntimeScene, @@ -43,7 +43,7 @@ namespace gdjs { runtimeScene ); this._renderer = new gdjs.LightRuntimeObjectRenderer(this, runtimeScene); - this._runtimeScene = runtimeScene; + this._instanceContainer = runtimeScene; // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. this.onCreated(); diff --git a/Extensions/LinkedObjects/linkedobjects.ts b/Extensions/LinkedObjects/linkedobjects.ts index 5046dcd63d..0e1fda9168 100644 --- a/Extensions/LinkedObjects/linkedobjects.ts +++ b/Extensions/LinkedObjects/linkedobjects.ts @@ -12,15 +12,17 @@ namespace gdjs { /** * Get the links manager of a scene. */ - static getManager(runtimeScene: gdjs.RuntimeScene): gdjs.LinksManager { + static getManager( + instanceContainer: gdjs.RuntimeInstanceContainer + ): gdjs.LinksManager { // @ts-ignore - if (!runtimeScene.linkedObjectsManager) { + if (!instanceContainer.linkedObjectsManager) { //Create the shared manager if necessary. // @ts-ignore - runtimeScene.linkedObjectsManager = new gdjs.LinksManager(); + instanceContainer.linkedObjectsManager = new gdjs.LinksManager(); } // @ts-ignore - return runtimeScene.linkedObjectsManager; + return instanceContainer.linkedObjectsManager; } /** @@ -184,44 +186,50 @@ namespace gdjs { export namespace evtTools { export namespace linkedObjects { - gdjs.registerObjectDeletedFromSceneCallback(function (runtimeScene, obj) { - LinksManager.getManager(runtimeScene).removeAllLinksOf(obj); + gdjs.registerObjectDeletedFromSceneCallback(function ( + instanceContainer, + obj + ) { + LinksManager.getManager(instanceContainer).removeAllLinksOf(obj); }); export const linkObjects = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, objA: gdjs.RuntimeObject, objB: gdjs.RuntimeObject ) { if (objA === null || objB === null) { return; } - LinksManager.getManager(runtimeScene).linkObjects(objA, objB); + LinksManager.getManager(instanceContainer).linkObjects(objA, objB); }; export const removeLinkBetween = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, objA: gdjs.RuntimeObject, objB: gdjs.RuntimeObject ) { if (objA === null || objB === null) { return; } - LinksManager.getManager(runtimeScene).removeLinkBetween(objA, objB); + LinksManager.getManager(instanceContainer).removeLinkBetween( + objA, + objB + ); }; export const removeAllLinksOf = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, objA: gdjs.RuntimeObject ) { if (objA === null) { return; } - LinksManager.getManager(runtimeScene).removeAllLinksOf(objA); + LinksManager.getManager(instanceContainer).removeAllLinksOf(objA); }; export const pickObjectsLinkedTo = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, objectsLists: Hashtable, obj: gdjs.RuntimeObject, eventsFunctionContext: EventsFunctionContext | undefined @@ -230,7 +238,7 @@ namespace gdjs { return false; } const linkedObjectMap = LinksManager.getManager( - runtimeScene + instanceContainer )._getMapOfObjectsLinkedWith(obj); let pickedSomething = false; @@ -273,7 +281,7 @@ namespace gdjs { // avoid running an intersection with the picked objects later. let objectCount = 0; for (const objectName of parentEventPickedObjectNames) { - objectCount += runtimeScene.getObjects(objectName).length; + objectCount += instanceContainer.getObjects(objectName)!.length; } if (parentEventPickedObjects.length === objectCount) { diff --git a/Extensions/PanelSpriteObject/panelspriteruntimeobject-pixi-renderer.ts b/Extensions/PanelSpriteObject/panelspriteruntimeobject-pixi-renderer.ts index 2305f31c2e..93c5019f47 100644 --- a/Extensions/PanelSpriteObject/panelspriteruntimeobject-pixi-renderer.ts +++ b/Extensions/PanelSpriteObject/panelspriteruntimeobject-pixi-renderer.ts @@ -20,12 +20,12 @@ namespace gdjs { constructor( runtimeObject: gdjs.PanelSpriteRuntimeObject, - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, textureName: string, tiled: boolean ) { this._object = runtimeObject; - const texture = (runtimeScene + const texture = (instanceContainer .getGame() .getImageManager() as gdjs.PixiImageManager).getPIXITexture( textureName @@ -58,14 +58,14 @@ namespace gdjs { ]; //Bottom-Right - this.setTexture(textureName, runtimeScene); + this.setTexture(textureName, instanceContainer); this._spritesContainer.removeChildren(); this._spritesContainer.addChild(this._centerSprite); for (let i = 0; i < this._borderSprites.length; ++i) { this._spritesContainer.addChild(this._borderSprites[i]); } this._wrapperContainer.addChild(this._spritesContainer); - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject(this._wrapperContainer, runtimeObject.getZOrder()); @@ -188,12 +188,19 @@ namespace gdjs { this._spritesContainer.cacheAsBitmap = false; } - setTexture(textureName, runtimeScene): void { + setTexture( + textureName: string, + instanceContainer: gdjs.RuntimeInstanceContainer + ): void { const obj = this._object; - const texture = runtimeScene + // @ts-ignore + const texture = instanceContainer .getGame() .getImageManager() - .getPIXITexture(textureName); + .getPIXITexture(textureName) as PIXI.BaseTexture< + PIXI.Resource, + PIXI.IAutoDetectOptions + >; this._textureWidth = texture.width; this._textureHeight = texture.height; diff --git a/Extensions/PanelSpriteObject/panelspriteruntimeobject.ts b/Extensions/PanelSpriteObject/panelspriteruntimeobject.ts index 1874455f5d..18b441ce38 100644 --- a/Extensions/PanelSpriteObject/panelspriteruntimeobject.ts +++ b/Extensions/PanelSpriteObject/panelspriteruntimeobject.ts @@ -43,14 +43,14 @@ namespace gdjs { _renderer: gdjs.PanelSpriteRuntimeObjectRenderer; /** - * @param runtimeScene The scene the object belongs to. + * @param instanceContainer The container the object belongs to. * @param panelSpriteObjectData The initial properties of the object */ constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, panelSpriteObjectData: PanelSpriteObjectData ) { - super(runtimeScene, panelSpriteObjectData); + super(instanceContainer, panelSpriteObjectData); this._rBorder = panelSpriteObjectData.rightMargin; this._lBorder = panelSpriteObjectData.leftMargin; this._tBorder = panelSpriteObjectData.topMargin; @@ -60,7 +60,7 @@ namespace gdjs { this._height = panelSpriteObjectData.height; this._renderer = new gdjs.PanelSpriteRuntimeObjectRenderer( this, - runtimeScene, + instanceContainer, panelSpriteObjectData.texture, panelSpriteObjectData.tiled ); @@ -100,7 +100,7 @@ namespace gdjs { updateTexture = true; } if (updateTexture) { - this.setTexture(newObjectData.texture, this._runtimeScene); + this.setTexture(newObjectData.texture, this.getRuntimeScene()); } if (oldObjectData.tiled !== newObjectData.tiled) { return false; @@ -112,8 +112,8 @@ namespace gdjs { return this._renderer.getRendererObject(); } - onDestroyFromScene(runtimeScene): void { - super.onDestroyFromScene(runtimeScene); + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { + super.onDestroyFromScene(instanceContainer); // @ts-ignore if (this._renderer.onDestroy) { // @ts-ignore @@ -121,7 +121,7 @@ namespace gdjs { } } - update(runtimeScene: gdjs.RuntimeScene): void { + update(instanceContainer: gdjs.RuntimeInstanceContainer): void { this._renderer.ensureUpToDate(); } @@ -156,10 +156,13 @@ namespace gdjs { /** * Set the texture of the panel sprite. * @param textureName The name of the texture. - * @param runtimeScene The scene the object lives in. + * @param instanceContainer The container the object lives in. */ - setTexture(textureName: string, runtimeScene: gdjs.RuntimeScene): void { - this._renderer.setTexture(textureName, runtimeScene); + setTexture( + textureName: string, + instanceContainer: gdjs.RuntimeInstanceContainer + ): void { + this._renderer.setTexture(textureName, instanceContainer); } /** @@ -196,7 +199,7 @@ namespace gdjs { this._width = width; this._renderer.updateWidth(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -208,7 +211,7 @@ namespace gdjs { this._height = height; this._renderer.updateHeight(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** diff --git a/Extensions/ParticleSystem/particleemitterobject-pixi-renderer.ts b/Extensions/ParticleSystem/particleemitterobject-pixi-renderer.ts index f9106bae1c..03f2c50c08 100644 --- a/Extensions/ParticleSystem/particleemitterobject-pixi-renderer.ts +++ b/Extensions/ParticleSystem/particleemitterobject-pixi-renderer.ts @@ -12,7 +12,7 @@ namespace gdjs { started: boolean = false; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, runtimeObject: gdjs.RuntimeObject, objectData: any ) { @@ -40,7 +40,7 @@ namespace gdjs { ); } else if (objectData.textureParticleName) { const sprite = new PIXI.Sprite( - (runtimeScene + (instanceContainer .getGame() .getImageManager() as gdjs.PixiImageManager).getPIXITexture( objectData.textureParticleName @@ -62,7 +62,7 @@ namespace gdjs { // Render the texture from graphics using the PIXI Renderer. // TODO: could be optimized by generating the texture only once per object type, // instead of at each object creation. - const pixiRenderer = runtimeScene + const pixiRenderer = instanceContainer .getGame() .getRenderer() .getPIXIRenderer(); @@ -167,7 +167,7 @@ namespace gdjs { // @ts-ignore this.emitter = new PIXI.particles.Emitter(this.renderer, texture, config); this.start(); - const layer = runtimeScene.getLayer(runtimeObject.getLayer()); + const layer = instanceContainer.getLayer(runtimeObject.getLayer()); if (layer) { layer .getRenderer() @@ -267,25 +267,28 @@ namespace gdjs { isTextureNameValid( texture: string, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): boolean { - const invalidPixiTexture = runtimeScene + const invalidPixiTexture = instanceContainer .getGame() .getImageManager() .getInvalidPIXITexture(); - const pixiTexture = runtimeScene + const pixiTexture = instanceContainer .getGame() .getImageManager() .getPIXITexture(texture); return pixiTexture.valid && pixiTexture !== invalidPixiTexture; } - setTextureName(texture: string, runtimeScene: gdjs.RuntimeScene): void { - const invalidPixiTexture = runtimeScene + setTextureName( + texture: string, + instanceContainer: gdjs.RuntimeInstanceContainer + ): void { + const invalidPixiTexture = instanceContainer .getGame() .getImageManager() .getInvalidPIXITexture(); - const pixiTexture = runtimeScene + const pixiTexture = instanceContainer .getGame() .getImageManager() .getPIXITexture(texture); diff --git a/Extensions/ParticleSystem/particleemitterobject.ts b/Extensions/ParticleSystem/particleemitterobject.ts index 2396545925..0321bcb8c4 100644 --- a/Extensions/ParticleSystem/particleemitterobject.ts +++ b/Extensions/ParticleSystem/particleemitterobject.ts @@ -97,12 +97,12 @@ namespace gdjs { * @param particleObjectData The initial properties of the object */ constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, particleObjectData: ParticleEmitterObjectData ) { - super(runtimeScene, particleObjectData); + super(instanceContainer, particleObjectData); this._renderer = new gdjs.ParticleEmitterObjectRenderer( - runtimeScene, + instanceContainer, this, particleObjectData ); @@ -231,7 +231,10 @@ namespace gdjs { if ( oldObjectData.textureParticleName !== newObjectData.textureParticleName ) { - this.setTexture(newObjectData.textureParticleName, this._runtimeScene); + this.setTexture( + newObjectData.textureParticleName, + this.getRuntimeScene() + ); } if (oldObjectData.flow !== newObjectData.flow) { this.setFlow(newObjectData.flow); @@ -259,7 +262,7 @@ namespace gdjs { oldObjectData.rendererParam2 !== newObjectData.rendererParam2 ) { // Destroy the renderer, ensure it's removed from the layer. - const layer = this._runtimeScene.getLayer(this.layer); + const layer = this.getInstanceContainer().getLayer(this.layer); layer .getRenderer() .removeRendererObject(this._renderer.getRendererObject()); @@ -267,7 +270,7 @@ namespace gdjs { // and recreate the renderer, which will add itself to the layer. this._renderer = new gdjs.ParticleEmitterObjectRenderer( - this._runtimeScene, + this.getInstanceContainer(), this, newObjectData ); @@ -281,7 +284,7 @@ namespace gdjs { return true; } - update(runtimeScene): void { + update(instanceContainer: gdjs.RuntimeInstanceContainer): void { if (this._posDirty) { this._renderer.setPosition(this.getX(), this.getY()); } @@ -324,24 +327,24 @@ namespace gdjs { this._renderer.resetEmission(this.flow, this.tank); } if (this._textureDirty) { - this._renderer.setTextureName(this.texture, runtimeScene); + this._renderer.setTextureName(this.texture, instanceContainer); } this._posDirty = this._angleDirty = this._forceDirty = this._zoneRadiusDirty = false; this._lifeTimeDirty = this._gravityDirty = this._colorDirty = this._sizeDirty = false; this._alphaDirty = this._flowDirty = this._textureDirty = this._tankDirty = false; - this._renderer.update(this.getElapsedTime(runtimeScene) / 1000.0); + this._renderer.update(this.getElapsedTime() / 1000.0); if ( this._renderer.hasStarted() && this.getParticleCount() === 0 && this.destroyWhenNoParticles ) { - this.deleteFromScene(runtimeScene); + this.deleteFromScene(instanceContainer); } } - onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void { + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { this._renderer.destroy(); - super.onDestroyFromScene(runtimeScene); + super.onDestroyFromScene(instanceContainer); } getEmitterForceMin(): number { @@ -734,9 +737,12 @@ namespace gdjs { return this.texture; } - setTexture(texture: string, runtimeScene: gdjs.RuntimeScene): void { + setTexture( + texture: string, + instanceContainer: gdjs.RuntimeInstanceContainer + ): void { if (this.texture !== texture) { - if (this._renderer.isTextureNameValid(texture, runtimeScene)) { + if (this._renderer.isTextureNameValid(texture, instanceContainer)) { this.texture = texture; this._textureDirty = true; } diff --git a/Extensions/PathfindingBehavior/pathfindingobstacleruntimebehavior.ts b/Extensions/PathfindingBehavior/pathfindingobstacleruntimebehavior.ts index 2af3091806..612996434a 100644 --- a/Extensions/PathfindingBehavior/pathfindingobstacleruntimebehavior.ts +++ b/Extensions/PathfindingBehavior/pathfindingobstacleruntimebehavior.ts @@ -4,35 +4,37 @@ Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com) */ namespace gdjs { + export interface RuntimeInstanceContainer { + pathfindingObstaclesManager: gdjs.PathfindingObstaclesManager; + } declare var rbush: any; /** - * PathfindingObstaclesManager manages the common objects shared by objects having a - * pathfinding behavior: In particular, the obstacles behaviors are required to declare - * themselves (see `PathfindingObstaclesManager.addObstacle`) to the manager of their associated scene - * (see `gdjs.PathfindingRuntimeBehavior.obstaclesManagers`). + * PathfindingObstaclesManager manages the common objects shared by objects + * having a pathfinding behavior: In particular, the obstacles behaviors are + * required to declare themselves (see + * `PathfindingObstaclesManager.addObstacle`) to the manager of their + * associated container (see + * `gdjs.PathfindingRuntimeBehavior.obstaclesManagers`). */ export class PathfindingObstaclesManager { _obstaclesRBush: any; - /** - * @param object The object - */ - constructor(runtimeScene: gdjs.RuntimeScene) { + constructor(instanceContainer: gdjs.RuntimeInstanceContainer) { this._obstaclesRBush = new rbush(); } /** - * Get the obstacles manager of a scene. + * Get the obstacles manager of an instance container. */ - static getManager(runtimeScene) { - if (!runtimeScene.pathfindingObstaclesManager) { + static getManager(instanceContainer: gdjs.RuntimeInstanceContainer) { + if (!instanceContainer.pathfindingObstaclesManager) { //Create the shared manager if necessary. - runtimeScene.pathfindingObstaclesManager = new gdjs.PathfindingObstaclesManager( - runtimeScene + instanceContainer.pathfindingObstaclesManager = new gdjs.PathfindingObstaclesManager( + instanceContainer ); } - return runtimeScene.pathfindingObstaclesManager; + return instanceContainer.pathfindingObstaclesManager; } /** @@ -111,14 +113,14 @@ namespace gdjs { > | null = null; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData, owner: gdjs.RuntimeObject ) { - super(runtimeScene, behaviorData, owner); + super(instanceContainer, behaviorData, owner); this._impassable = behaviorData.impassable; this._cost = behaviorData.cost; - this._manager = PathfindingObstaclesManager.getManager(runtimeScene); + this._manager = PathfindingObstaclesManager.getManager(instanceContainer); //Note that we can't use getX(), getWidth()... of owner here: //The owner is not yet fully constructed. @@ -140,7 +142,7 @@ namespace gdjs { } } - doStepPreEvents(runtimeScene: gdjs.RuntimeScene) { + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { //Make sure the obstacle is or is not in the obstacles manager. if (!this.activated() && this._registeredInManager) { this._manager.removeObstacle(this); @@ -170,7 +172,7 @@ namespace gdjs { } } - doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {} + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {} getAABB() { return this.owner.getAABB(); diff --git a/Extensions/PathfindingBehavior/pathfindingruntimebehavior.ts b/Extensions/PathfindingBehavior/pathfindingruntimebehavior.ts index 61db26a8fc..376a7464d8 100644 --- a/Extensions/PathfindingBehavior/pathfindingruntimebehavior.ts +++ b/Extensions/PathfindingBehavior/pathfindingruntimebehavior.ts @@ -38,11 +38,11 @@ namespace gdjs { _movementAngle: float = 0; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData, owner: gdjs.RuntimeObject ) { - super(runtimeScene, behaviorData, owner); + super(instanceContainer, behaviorData, owner); //The path computed and followed by the object (Array of arrays containing x and y position) if (this._path === undefined) { @@ -60,7 +60,9 @@ namespace gdjs { this._gridOffsetX = behaviorData.gridOffsetX || 0; this._gridOffsetY = behaviorData.gridOffsetY || 0; this._extraBorder = behaviorData.extraBorder; - this._manager = gdjs.PathfindingObstaclesManager.getManager(runtimeScene); + this._manager = gdjs.PathfindingObstaclesManager.getManager( + instanceContainer + ); this._searchContext = new gdjs.PathfindingRuntimeBehavior.SearchContext( this._manager ); @@ -313,7 +315,11 @@ namespace gdjs { /** * Compute and move on the path to the specified destination. */ - moveTo(runtimeScene: gdjs.RuntimeScene, x: float, y: float) { + moveTo( + instanceContainer: gdjs.RuntimeInstanceContainer, + x: float, + y: float + ) { const owner = this.owner; //First be sure that there is a path to compute. @@ -403,13 +409,13 @@ namespace gdjs { } } - doStepPreEvents(runtimeScene: gdjs.RuntimeScene) { + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { if (this._path.length === 0 || this._reachedEnd) { return; } // Update the speed of the object - const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000; + const timeDelta = this.owner.getElapsedTime() / 1000; const previousSpeed = this._speed; if (this._speed !== this._maxSpeed) { this._speed += this._acceleration * timeDelta; @@ -452,8 +458,7 @@ namespace gdjs { ) { this.owner.rotateTowardAngle( this._movementAngle + this._angleOffset, - this._angularSpeed, - runtimeScene + this._angularSpeed ); } } else { @@ -463,7 +468,7 @@ namespace gdjs { this.owner.setY(newPos[1]); } - doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {} + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {} /** * Compute the euclidean distance between two positions. diff --git a/Extensions/PathfindingBehavior/tests/legacypathfindingruntimebehavior.spec.js b/Extensions/PathfindingBehavior/tests/legacypathfindingruntimebehavior.spec.js index 318878b022..c8cb84ada5 100644 --- a/Extensions/PathfindingBehavior/tests/legacypathfindingruntimebehavior.spec.js +++ b/Extensions/PathfindingBehavior/tests/legacypathfindingruntimebehavior.spec.js @@ -61,9 +61,14 @@ describe('gdjs.PathfindingRuntimeBehavior', function () { sprites: [ { originPoint: objectCenteredOnCells - ? { x: 80, y: 80 } - : { x: 87, y: 87 }, - centerPoint: { x: 80, y: 80 }, + ? { name: 'Origin', x: 80, y: 80 } + : { name: 'Origin', x: 87, y: 87 }, + centerPoint: { + name: 'Center', + x: 80, + y: 80, + automatic: false, + }, points: [ { name: 'Center', x: 80, y: 80 }, objectCenteredOnCells @@ -71,13 +76,17 @@ describe('gdjs.PathfindingRuntimeBehavior', function () { : { name: 'Origin', x: 87, y: 87 }, ], hasCustomCollisionMask: false, + customCollisionMask: [], + image: '', }, ], + timeBetweenFrames: 0, + looping: false, }, ], + useMultipleDirections: false, }, ], - effects: [], behaviors: [ { type: 'PathfindingBehavior::PathfindingBehavior', @@ -93,6 +102,9 @@ describe('gdjs.PathfindingRuntimeBehavior', function () { extraBorder: 0, }, ], + variables: [], + effects: [], + updateIfNotVisible: true, }); player.getWidth = function () { return 160; @@ -116,9 +128,14 @@ describe('gdjs.PathfindingRuntimeBehavior', function () { sprites: [ { originPoint: objectCenteredOnCells - ? { x: 80, y: 80 } - : { x: 87, y: 87 }, - centerPoint: { x: 80, y: 80 }, + ? { name: 'Origin', x: 80, y: 80 } + : { name: 'Origin', x: 87, y: 87 }, + centerPoint: { + name: 'Center', + x: 80, + y: 80, + automatic: false, + }, points: [ { name: 'Center', x: 80, y: 80 }, objectCenteredOnCells @@ -126,13 +143,17 @@ describe('gdjs.PathfindingRuntimeBehavior', function () { : { name: 'Origin', x: 87, y: 87 }, ], hasCustomCollisionMask: false, + customCollisionMask: [], + image: '', }, ], + timeBetweenFrames: 0, + looping: false, }, ], + useMultipleDirections: false, }, ], - effects: [], behaviors: [ { type: 'PathfindingBehavior::PathfindingObstacleBehavior', @@ -140,6 +161,9 @@ describe('gdjs.PathfindingRuntimeBehavior', function () { cost: 2, }, ], + variables: [], + effects: [], + updateIfNotVisible: true, }); obstacle.getWidth = function () { return 160; diff --git a/Extensions/Physics2Behavior/physics2runtimebehavior.ts b/Extensions/Physics2Behavior/physics2runtimebehavior.ts index 3b42e637da..9c4257b6ba 100644 --- a/Extensions/Physics2Behavior/physics2runtimebehavior.ts +++ b/Extensions/Physics2Behavior/physics2runtimebehavior.ts @@ -35,7 +35,7 @@ namespace gdjs { */ _registeredBehaviors: Set; - constructor(runtimeScene: gdjs.RuntimeScene, sharedData) { + constructor(instanceContainer: gdjs.RuntimeInstanceContainer, sharedData) { this._registeredBehaviors = new Set(); this.gravityX = sharedData.gravityX; this.gravityY = sharedData.gravityY; @@ -349,11 +349,11 @@ namespace gdjs { _verticesBuffer: integer = 0; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData, owner: gdjs.RuntimeObject ) { - super(runtimeScene, behaviorData, owner); + super(instanceContainer, behaviorData, owner); this.bodyType = behaviorData.bodyType; this.bullet = behaviorData.bullet; this.fixedRotation = behaviorData.fixedRotation; @@ -382,7 +382,7 @@ namespace gdjs { this.currentContacts.length = 0; this.destroyedDuringFrameLogic = false; this._sharedData = Physics2SharedData.getSharedData( - runtimeScene, + instanceContainer.getScene(), behaviorData.name ); this._tempb2Vec2 = new Box2D.b2Vec2(); @@ -824,14 +824,15 @@ namespace gdjs { return true; } - doStepPreEvents(runtimeScene: gdjs.RuntimeScene) { + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { // Step the world if not done this frame yet if (!this._sharedData.stepped) { // Reset started and ended contacts array for all physics instances. this._sharedData.resetStartedAndEndedCollisions(); this._sharedData.updateBodiesFromObjects(); this._sharedData.step( - runtimeScene.getTimeManager().getElapsedTime() / 1000.0 + instanceContainer.getScene().getTimeManager().getElapsedTime() / + 1000.0 ); } @@ -862,7 +863,7 @@ namespace gdjs { this._objectOldAngle = this.owner.getAngle(); } - doStepPostEvents(runtimeScene: gdjs.RuntimeScene) { + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { // Reset world step to update next frame this._sharedData.stepped = false; } diff --git a/Extensions/PlatformBehavior/platformerobjectruntimebehavior.ts b/Extensions/PlatformBehavior/platformerobjectruntimebehavior.ts index 6432c667df..64acc595b9 100644 --- a/Extensions/PlatformBehavior/platformerobjectruntimebehavior.ts +++ b/Extensions/PlatformBehavior/platformerobjectruntimebehavior.ts @@ -117,11 +117,11 @@ namespace gdjs { private _manager: gdjs.PlatformObjectsManager; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData, owner: gdjs.RuntimeObject ) { - super(runtimeScene, behaviorData, owner); + super(instanceContainer, behaviorData, owner); this._gravity = behaviorData.gravity; this._maxFallingSpeed = behaviorData.maxFallingSpeed; this._ladderClimbingSpeed = behaviorData.ladderClimbingSpeed || 150; @@ -146,7 +146,7 @@ namespace gdjs { this._potentialCollidingObjects = []; this._overlappedJumpThru = []; - this._manager = gdjs.PlatformObjectsManager.getManager(runtimeScene); + this._manager = gdjs.PlatformObjectsManager.getManager(instanceContainer); this._falling = new Falling(this); this._onFloor = new OnFloor(this); @@ -210,7 +210,7 @@ namespace gdjs { return true; } - doStepPreEvents(runtimeScene: gdjs.RuntimeScene) { + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { const LEFTKEY = 37; const UPKEY = 38; const RIGHTKEY = 39; @@ -219,13 +219,13 @@ namespace gdjs { const RSHIFTKEY = 2016; const SPACEKEY = 32; const object = this.owner; - const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000; + const timeDelta = this.owner.getElapsedTime() / 1000; //0.1) Get the player input: this._requestedDeltaX = 0; this._requestedDeltaY = 0; - const inputManager = runtimeScene.getGame().getInputManager(); + const inputManager = instanceContainer.getGame().getInputManager(); this._leftKey || (this._leftKey = !this._ignoreDefaultControls && inputManager.isKeyPressed(LEFTKEY)); @@ -329,7 +329,7 @@ namespace gdjs { this._lastDeltaY = object.getY() - oldY; } - doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {} + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {} private _updateSpeed(timeDelta: float): float { const previousSpeed = this._currentSpeed; diff --git a/Extensions/PlatformBehavior/platformruntimebehavior.ts b/Extensions/PlatformBehavior/platformruntimebehavior.ts index f735eaa88f..e4824a1bb8 100644 --- a/Extensions/PlatformBehavior/platformruntimebehavior.ts +++ b/Extensions/PlatformBehavior/platformruntimebehavior.ts @@ -8,34 +8,31 @@ namespace gdjs { /** * Manages the common objects shared by objects having a - * platform behavior: in particular, the platforms behaviors are required to declare - * themselves (see PlatformObjectsManager.addPlatform) to the manager of their associated scene - * (see PlatformRuntimeBehavior.getManager). + * platform behavior: in particular, the platforms behaviors are required to + * declare themselves (see PlatformObjectsManager.addPlatform) to the manager + * of their associated container (see PlatformRuntimeBehavior.getManager). */ export class PlatformObjectsManager { private _platformRBush: any; - /** - * @param object The object - */ - constructor(runtimeScene: gdjs.RuntimeScene) { + constructor(instanceContainer: gdjs.RuntimeInstanceContainer) { this._platformRBush = new rbush(); } /** - * Get the platforms manager of a scene. + * Get the platforms manager of an instance container. */ - static getManager(runtimeScene: gdjs.RuntimeScene) { + static getManager(instanceContainer: gdjs.RuntimeInstanceContainer) { // @ts-ignore - if (!runtimeScene.platformsObjectsManager) { + if (!instanceContainer.platformsObjectsManager) { //Create the shared manager if necessary. // @ts-ignore - runtimeScene.platformsObjectsManager = new gdjs.PlatformObjectsManager( - runtimeScene + instanceContainer.platformsObjectsManager = new gdjs.PlatformObjectsManager( + instanceContainer ); } // @ts-ignore - return runtimeScene.platformsObjectsManager; + return instanceContainer.platformsObjectsManager; } /** @@ -132,11 +129,11 @@ namespace gdjs { _registeredInManager: boolean = false; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData, owner: gdjs.RuntimeObject ) { - super(runtimeScene, behaviorData, owner); + super(instanceContainer, behaviorData, owner); this._platformType = behaviorData.platformType; if (behaviorData.platformType === 'Ladder') { this._platformType = PlatformRuntimeBehavior.LADDER; @@ -147,7 +144,7 @@ namespace gdjs { } this._canBeGrabbed = behaviorData.canBeGrabbed || false; this._yGrabOffset = behaviorData.yGrabOffset || 0; - this._manager = PlatformObjectsManager.getManager(runtimeScene); + this._manager = PlatformObjectsManager.getManager(instanceContainer); } updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { @@ -169,7 +166,7 @@ namespace gdjs { } } - doStepPreEvents(runtimeScene: gdjs.RuntimeScene) { + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { //Scene change is not supported /*if ( parentScene != &scene ) //Parent scene has changed { @@ -211,7 +208,7 @@ namespace gdjs { } } - doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {} + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {} onActivate() { if (this._registeredInManager) { diff --git a/Extensions/PlayerAuthentication/playerauthenticationtools.ts b/Extensions/PlayerAuthentication/playerauthenticationtools.ts index 4191f3fbe1..5eedf5cac9 100644 --- a/Extensions/PlayerAuthentication/playerauthenticationtools.ts +++ b/Extensions/PlayerAuthentication/playerauthenticationtools.ts @@ -3,6 +3,7 @@ namespace gdjs { const logger = new gdjs.Logger('Player Authentication'); const authComponents = gdjs.playerAuthenticationComponents; + // TODO EBO Replace runtimeScene to instanceContainer. export namespace playerAuthentication { // Authentication information. let _username: string | null = null; diff --git a/Extensions/PrimitiveDrawing/shapepainterruntimeobject-pixi-renderer.ts b/Extensions/PrimitiveDrawing/shapepainterruntimeobject-pixi-renderer.ts index 7695a149b7..690a996d1f 100644 --- a/Extensions/PrimitiveDrawing/shapepainterruntimeobject-pixi-renderer.ts +++ b/Extensions/PrimitiveDrawing/shapepainterruntimeobject-pixi-renderer.ts @@ -27,11 +27,11 @@ namespace gdjs { constructor( runtimeObject: gdjs.ShapePainterRuntimeObject, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { this._object = runtimeObject; this._graphics = new PIXI.Graphics(); - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject(this._graphics, runtimeObject.getZOrder()); diff --git a/Extensions/PrimitiveDrawing/shapepainterruntimeobject.ts b/Extensions/PrimitiveDrawing/shapepainterruntimeobject.ts index 00d5e05b43..345ce1ff8c 100644 --- a/Extensions/PrimitiveDrawing/shapepainterruntimeobject.ts +++ b/Extensions/PrimitiveDrawing/shapepainterruntimeobject.ts @@ -57,14 +57,14 @@ namespace gdjs { private static readonly _pointForTransformation: FloatPoint = [0, 0]; /** - * @param runtimeScene The scene the object belongs to. + * @param instanceContainer The container the object belongs to. * @param shapePainterObjectData The initial properties of the object */ constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, shapePainterObjectData: ShapePainterObjectData ) { - super(runtimeScene, shapePainterObjectData); + super(instanceContainer, shapePainterObjectData); this._fillColor = parseInt( gdjs.rgbToHex( shapePainterObjectData.fillColor.r, @@ -88,7 +88,7 @@ namespace gdjs { this._clearBetweenFrames = shapePainterObjectData.clearBetweenFrames; this._renderer = new gdjs.ShapePainterRuntimeObjectRenderer( this, - runtimeScene + instanceContainer ); // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. @@ -158,12 +158,12 @@ namespace gdjs { return true; } - stepBehaviorsPreEvents(runtimeScene: gdjs.RuntimeScene) { + stepBehaviorsPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { //We redefine stepBehaviorsPreEvents just to clear the graphics before running events. if (this._clearBetweenFrames) { this.clear(); } - super.stepBehaviorsPreEvents(runtimeScene); + super.stepBehaviorsPreEvents(instanceContainer); } /** @@ -468,7 +468,7 @@ namespace gdjs { } super.setAngle(angle); this._renderer.updateAngle(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -584,7 +584,7 @@ namespace gdjs { } this._scaleX = newScale * (this._flippedX ? -1 : 1); this._renderer.updateScaleX(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -601,7 +601,7 @@ namespace gdjs { } this._scaleY = newScale * (this._flippedY ? -1 : 1); this._renderer.updateScaleY(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } flipX(enable: boolean): void { @@ -609,7 +609,7 @@ namespace gdjs { this._scaleX *= -1; this._flippedX = enable; this._renderer.updateScaleX(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } } @@ -618,7 +618,7 @@ namespace gdjs { this._scaleY *= -1; this._flippedY = enable; this._renderer.updateScaleY(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } } @@ -660,7 +660,7 @@ namespace gdjs { } invalidateBounds() { - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } getDrawableX(): float { @@ -679,7 +679,7 @@ namespace gdjs { return this._renderer.getHeight(); } - updatePreRender(runtimeScene: gdjs.RuntimeScene): void { + updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer): void { this._renderer.updatePreRender(); } @@ -741,7 +741,7 @@ namespace gdjs { rectangle[3][0] = left; rectangle[3][1] = bottom; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } updateHitBoxes(): void { diff --git a/Extensions/Screenshot/screenshottools.ts b/Extensions/Screenshot/screenshottools.ts index 0446da6c51..5cf7f59d6e 100644 --- a/Extensions/Screenshot/screenshottools.ts +++ b/Extensions/Screenshot/screenshottools.ts @@ -4,16 +4,16 @@ namespace gdjs { /** * Save a screenshot of the game. - * @param runtimeScene The scene + * @param instanceContainer The container * @param savePath The path where to save the screenshot */ export const takeScreenshot = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, savePath: string ) { const fs = typeof require !== 'undefined' ? require('fs') : null; if (fs) { - const canvas = runtimeScene.getGame().getRenderer().getCanvas(); + const canvas = instanceContainer.getGame().getRenderer().getCanvas(); if (canvas) { const content = canvas .toDataURL('image/png') diff --git a/Extensions/Shopify/shopifytools.ts b/Extensions/Shopify/shopifytools.ts index 6901e5a9da..ba786020b2 100644 --- a/Extensions/Shopify/shopifytools.ts +++ b/Extensions/Shopify/shopifytools.ts @@ -1,17 +1,24 @@ namespace gdjs { + export interface RuntimeGame { + shopifyClients: { [name: string]: any }; + } declare var ShopifyBuy: any; export class ShopifyClientsManager { - static set(runtimeScene, name, shopifyClient) { - const game = runtimeScene.getGame(); + static set( + instanceContainer: gdjs.RuntimeInstanceContainer, + name: string, + shopifyClient + ) { + const game = instanceContainer.getGame(); if (!game.shopifyClients) { game.shopifyClients = {}; } game.shopifyClients[name] = shopifyClient; } - static get(runtimeScene, name) { - const game = runtimeScene.getGame(); + static get(instanceContainer: gdjs.RuntimeInstanceContainer, name: string) { + const game = instanceContainer.getGame(); if (!game.shopifyClients) { game.shopifyClients = {}; } @@ -22,11 +29,11 @@ namespace gdjs { export namespace evtTools { export namespace shopify { export const buildClient = function ( - runtimeScene, - name, - domain, - appId, - accessToken + instanceContainer: gdjs.RuntimeInstanceContainer, + name: string, + domain: string, + appId: string, + accessToken: string ) { if (typeof ShopifyBuy === 'undefined') { return; @@ -37,21 +44,24 @@ namespace gdjs { appId: appId, }); const shopifyClient = ShopifyBuy.buildClient(config); - ShopifyClientsManager.set(runtimeScene, name, shopifyClient); + ShopifyClientsManager.set(instanceContainer, name, shopifyClient); }; export const getCheckoutUrlForProduct = function ( - runtimeScene, - name, - productId, - quantity, - variantIndex, - successVariable, - errorVariable + instanceContainer: gdjs.RuntimeInstanceContainer, + name: string, + productId: string, + quantity: number, + variantIndex: number, + successVariable: gdjs.Variable, + errorVariable: gdjs.Variable ) { errorVariable.setString(''); successVariable.setString(''); - const shopifyClient = ShopifyClientsManager.get(runtimeScene, name); + const shopifyClient = ShopifyClientsManager.get( + instanceContainer, + name + ); shopifyClient.fetchProduct(productId).then( function (product) { if (variantIndex < 0 || variantIndex >= product.variants.length) { diff --git a/Extensions/SpatialSound/spatialsoundtools.ts b/Extensions/SpatialSound/spatialsoundtools.ts index 8b753f75f4..3d74240e12 100644 --- a/Extensions/SpatialSound/spatialsoundtools.ts +++ b/Extensions/SpatialSound/spatialsoundtools.ts @@ -3,13 +3,17 @@ namespace gdjs { export namespace spatialSound { const logger = new gdjs.Logger('Spatial Sound'); export const setSoundPosition = ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, channel: integer, x: float, y: float, z: float ) => { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + // TODO EBO The position must be transform to the scene position when it comes from a custom object. + const sound = instanceContainer + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) sound.setSpatialPosition(x, y, z); else logger.error( diff --git a/Extensions/SystemInfo/systeminfotools.ts b/Extensions/SystemInfo/systeminfotools.ts index eccb8ca954..a9ae81138a 100644 --- a/Extensions/SystemInfo/systeminfotools.ts +++ b/Extensions/SystemInfo/systeminfotools.ts @@ -83,18 +83,20 @@ namespace gdjs { * @returns true if WebGL is supported */ export const isWebGLSupported = ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): boolean => { - return runtimeScene.getGame().getRenderer().isWebGLSupported(); + return instanceContainer.getGame().getRenderer().isWebGLSupported(); }; /** * Check if the game is running as a preview, launched from an editor. - * @param runtimeScene The current scene. + * @param instanceContainer The current container. * @returns true if the game is running as a preview. */ - export const isPreview = (runtimeScene: gdjs.RuntimeScene): boolean => { - return runtimeScene.getGame().isPreview(); + export const isPreview = ( + instanceContainer: gdjs.RuntimeInstanceContainer + ): boolean => { + return instanceContainer.getGame().isPreview(); }; } } diff --git a/Extensions/TextEntryObject/textentryruntimeobject-pixi-renderer.ts b/Extensions/TextEntryObject/textentryruntimeobject-pixi-renderer.ts index a494823a20..fdc4980549 100644 --- a/Extensions/TextEntryObject/textentryruntimeobject-pixi-renderer.ts +++ b/Extensions/TextEntryObject/textentryruntimeobject-pixi-renderer.ts @@ -1,11 +1,11 @@ namespace gdjs { class TextEntryRuntimeObjectPixiRenderer { - _object: any; + _object: gdjs.TextEntryRuntimeObject; _pressHandler: any; _upHandler: any; _downHandler: any; - constructor(runtimeObject) { + constructor(runtimeObject: gdjs.TextEntryRuntimeObject) { this._object = runtimeObject; this._pressHandler = function (evt) { if (!runtimeObject.isActivated()) { diff --git a/Extensions/TextEntryObject/textentryruntimeobject.ts b/Extensions/TextEntryObject/textentryruntimeobject.ts index ffe3e32df5..5ab4362289 100644 --- a/Extensions/TextEntryObject/textentryruntimeobject.ts +++ b/Extensions/TextEntryObject/textentryruntimeobject.ts @@ -12,14 +12,14 @@ namespace gdjs { _renderer: gdjs.TextEntryRuntimeObjectRenderer; /** - * @param runtimeScene The scene the object belongs to. + * @param instanceContainer The container the object belongs to. * @param textEntryObjectData The initial properties of the object */ constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, textEntryObjectData: ObjectData ) { - super(runtimeScene, textEntryObjectData); + super(instanceContainer, textEntryObjectData); this._renderer = new gdjs.TextEntryRuntimeObjectRenderer(this); // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. @@ -31,14 +31,14 @@ namespace gdjs { return true; } - onDestroyFromScene(runtimeScene): void { - super.onDestroyFromScene(runtimeScene); + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { + super.onDestroyFromScene(instanceContainer); if (this._renderer.onDestroy) { this._renderer.onDestroy(); } } - update(runtimeScene: gdjs.RuntimeScene): void { + update(instanceContainer: gdjs.RuntimeInstanceContainer): void { if ((this._renderer as any).getString) { this._str = (this._renderer as any).getString(); } diff --git a/Extensions/TextInput/textinputruntimeobject-pixi-renderer.ts b/Extensions/TextInput/textinputruntimeobject-pixi-renderer.ts index bb15780a71..35aca3e729 100644 --- a/Extensions/TextInput/textinputruntimeobject-pixi-renderer.ts +++ b/Extensions/TextInput/textinputruntimeobject-pixi-renderer.ts @@ -29,13 +29,16 @@ namespace gdjs { class TextInputRuntimeObjectPixiRenderer { private _object: gdjs.TextInputRuntimeObject; private _input: HTMLInputElement | HTMLTextAreaElement | null = null; - private _runtimeScene: gdjs.RuntimeScene; + private _instanceContainer: gdjs.RuntimeInstanceContainer; private _runtimeGame: gdjs.RuntimeGame; - constructor(runtimeObject: gdjs.TextInputRuntimeObject) { + constructor( + runtimeObject: gdjs.TextInputRuntimeObject, + instanceContainer: gdjs.RuntimeInstanceContainer + ) { this._object = runtimeObject; - this._runtimeScene = runtimeObject.getRuntimeScene(); - this._runtimeGame = this._runtimeScene.getGame(); + this._instanceContainer = instanceContainer; + this._runtimeGame = this._instanceContainer.getGame(); this._createElement(); } @@ -123,51 +126,71 @@ namespace gdjs { // Hide the input entirely if the layer is not visible. // Because this object is rendered as a DOM element (and not part of the PixiJS // scene graph), we have to do this manually. - const layer = this._runtimeScene.getLayer(this._object.getLayer()); + const layer = this._instanceContainer.getLayer(this._object.getLayer()); if (!layer.isVisible()) { this._input.style.display = 'none'; return; } - const runtimeGame = this._runtimeScene.getGame(); + const workingPoint: FloatPoint = gdjs.staticArray( + TextInputRuntimeObjectPixiRenderer.prototype.updatePreRender + ) as FloatPoint; + + const runtimeGame = this._instanceContainer.getGame(); const runtimeGameRenderer = runtimeGame.getRenderer(); const topLeftCanvasCoordinates = layer.convertInverseCoords( this._object.x, this._object.y, - 0 + 0, + workingPoint ); + const canvasLeft = topLeftCanvasCoordinates[0]; + const canvasTop = topLeftCanvasCoordinates[1]; + const bottomRightCanvasCoordinates = layer.convertInverseCoords( this._object.x + this._object.getWidth(), this._object.y + this._object.getHeight(), - 0 + 0, + workingPoint ); + const canvasRight = bottomRightCanvasCoordinates[0]; + const canvasBottom = bottomRightCanvasCoordinates[1]; // Hide the input entirely if not visible at all. const isOutsideCanvas = - bottomRightCanvasCoordinates[0] < 0 || - bottomRightCanvasCoordinates[1] < 0 || - topLeftCanvasCoordinates[0] > runtimeGame.getGameResolutionWidth() || - topLeftCanvasCoordinates[1] > runtimeGame.getGameResolutionHeight(); + canvasRight < 0 || + canvasBottom < 0 || + canvasLeft > runtimeGame.getGameResolutionWidth() || + canvasTop > runtimeGame.getGameResolutionHeight(); if (isOutsideCanvas) { this._input.style.display = 'none'; return; } // Position the input on the container on top of the canvas. + workingPoint[0] = canvasLeft; + workingPoint[1] = canvasRight; const topLeftPageCoordinates = runtimeGameRenderer.convertCanvasToDomElementContainerCoords( - topLeftCanvasCoordinates + workingPoint, + workingPoint ); + const pageLeft = workingPoint[0]; + const pageTop = workingPoint[1]; + + workingPoint[0] = canvasRight; + workingPoint[1] = canvasBottom; const bottomRightPageCoordinates = runtimeGameRenderer.convertCanvasToDomElementContainerCoords( - bottomRightCanvasCoordinates + workingPoint, + workingPoint ); + const pageRight = workingPoint[0]; + const pageBottom = workingPoint[1]; - const widthInContainer = - bottomRightPageCoordinates[0] - topLeftPageCoordinates[0]; - const heightInContainer = - bottomRightPageCoordinates[1] - topLeftPageCoordinates[1]; + const widthInContainer = pageRight - pageLeft; + const heightInContainer = pageBottom - pageTop; - this._input.style.left = topLeftPageCoordinates[0] + 'px'; - this._input.style.top = topLeftPageCoordinates[1] + 'px'; + this._input.style.left = pageLeft + 'px'; + this._input.style.top = pageTop + 'px'; this._input.style.width = widthInContainer + 'px'; this._input.style.height = heightInContainer + 'px'; this._input.style.transform = @@ -195,7 +218,7 @@ namespace gdjs { updateFont() { if (!this._input) return; - this._input.style.fontFamily = this._runtimeScene + this._input.style.fontFamily = this._instanceContainer .getGame() .getFontManager() .getFontFamily(this._object.getFontResourceName()); diff --git a/Extensions/TextInput/textinputruntimeobject.ts b/Extensions/TextInput/textinputruntimeobject.ts index 507637b487..2e77478db9 100644 --- a/Extensions/TextInput/textinputruntimeobject.ts +++ b/Extensions/TextInput/textinputruntimeobject.ts @@ -71,10 +71,10 @@ namespace gdjs { _renderer: TextInputRuntimeObjectRenderer; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, objectData: TextInputObjectData ) { - super(runtimeScene, objectData); + super(instanceContainer, objectData); this._string = objectData.content.initialValue; this._placeholder = objectData.content.placeholder; @@ -92,7 +92,10 @@ namespace gdjs { this._disabled = objectData.content.disabled; this._readOnly = objectData.content.readOnly; - this._renderer = new gdjs.TextInputRuntimeObjectRenderer(this); + this._renderer = new gdjs.TextInputRuntimeObjectRenderer( + this, + instanceContainer + ); // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. this.onCreated(); @@ -167,7 +170,7 @@ namespace gdjs { return true; } - updatePreRender(runtimeScene: RuntimeScene): void { + updatePreRender(instanceContainer: RuntimeInstanceContainer): void { this._renderer.updatePreRender(); } @@ -196,8 +199,8 @@ namespace gdjs { this._renderer.onSceneResumed(); } - onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void { - super.onDestroyFromScene(runtimeScene); + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { + super.onDestroyFromScene(instanceContainer); this._renderer.onDestroy(); } diff --git a/Extensions/TextObject/textruntimeobject-pixi-renderer.ts b/Extensions/TextObject/textruntimeobject-pixi-renderer.ts index 24ab6209fd..4df001b56c 100644 --- a/Extensions/TextObject/textruntimeobject-pixi-renderer.ts +++ b/Extensions/TextObject/textruntimeobject-pixi-renderer.ts @@ -9,14 +9,14 @@ namespace gdjs { constructor( runtimeObject: gdjs.TextRuntimeObject, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { this._object = runtimeObject; - this._fontManager = runtimeScene.getGame().getFontManager(); + this._fontManager = instanceContainer.getGame().getFontManager(); this._text = new PIXI.Text(' ', { align: 'left' }); this._text.anchor.x = 0.5; this._text.anchor.y = 0.5; - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject(this._text, runtimeObject.getZOrder()); diff --git a/Extensions/TextObject/textruntimeobject.ts b/Extensions/TextObject/textruntimeobject.ts index 810e486509..ea1f36b912 100644 --- a/Extensions/TextObject/textruntimeobject.ts +++ b/Extensions/TextObject/textruntimeobject.ts @@ -63,14 +63,14 @@ namespace gdjs { _scaleY: number = 1; /** - * @param runtimeScene The scene the object belongs to. + * @param instanceContainer The container the object belongs to. * @param textObjectData The initial properties of the object */ constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, textObjectData: TextObjectData ) { - super(runtimeScene, textObjectData); + super(instanceContainer, textObjectData); this._characterSize = Math.max(1, textObjectData.characterSize); this._fontName = textObjectData.font; this._bold = textObjectData.bold; @@ -82,7 +82,10 @@ namespace gdjs { textObjectData.color.b, ]; this._str = textObjectData.string; - this._renderer = new gdjs.TextRuntimeObjectRenderer(this, runtimeScene); + this._renderer = new gdjs.TextRuntimeObjectRenderer( + this, + instanceContainer + ); // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. this.onCreated(); @@ -131,7 +134,7 @@ namespace gdjs { return this._renderer.getRendererObject(); } - update(runtimeScene: gdjs.RuntimeScene): void { + update(instanceContainer: gdjs.RuntimeInstanceContainer): void { this._renderer.ensureUpToDate(); } @@ -151,7 +154,7 @@ namespace gdjs { * Update the rendered object position. */ private _updateTextPosition() { - this.hitBoxesDirty = true; + this.invalidateHitboxes(); this._renderer.updatePosition(); } @@ -326,7 +329,7 @@ namespace gdjs { this._scaleX = newScale; this._scaleY = newScale; this._renderer.setScale(newScale); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -338,7 +341,7 @@ namespace gdjs { this._scaleX = newScale; this._renderer.setScaleX(newScale); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -350,7 +353,7 @@ namespace gdjs { this._scaleY = newScale; this._renderer.setScaleY(newScale); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -410,7 +413,7 @@ namespace gdjs { this._wrapping = enable; this._renderer.updateStyle(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -432,7 +435,7 @@ namespace gdjs { this._wrappingWidth = width; this._renderer.updateStyle(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** diff --git a/Extensions/TileMap/TileMapRuntimeManager.ts b/Extensions/TileMap/TileMapRuntimeManager.ts index 676c379889..af466048a8 100644 --- a/Extensions/TileMap/TileMapRuntimeManager.ts +++ b/Extensions/TileMap/TileMapRuntimeManager.ts @@ -1,6 +1,6 @@ /// namespace gdjs { - export interface RuntimeScene { + export interface RuntimeInstanceContainer { tileMapCollisionMaskManager: gdjs.TileMap.TileMapRuntimeManager; } export namespace TileMap { @@ -26,7 +26,7 @@ namespace gdjs { * @see {@link TileMapManager} */ export class TileMapRuntimeManager { - private _runtimeScene: gdjs.RuntimeScene; + private _instanceContainer: gdjs.RuntimeInstanceContainer; /** * Delegate that actually manage the caches without anything specific to * GDJS. @@ -34,27 +34,27 @@ namespace gdjs { */ private _manager: TileMapHelper.TileMapManager; /** - * @param runtimeScene The scene. + * @param instanceContainer The instance container. */ - private constructor(runtimeScene: gdjs.RuntimeScene) { - this._runtimeScene = runtimeScene; + private constructor(instanceContainer: gdjs.RuntimeInstanceContainer) { + this._instanceContainer = instanceContainer; this._manager = new TileMapHelper.TileMapManager(); } /** - * @param runtimeScene Where to set the manager instance. + * @param instanceContainer Where to set the manager instance. * @returns The shared manager. */ static getManager( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): TileMapRuntimeManager { - if (!runtimeScene.tileMapCollisionMaskManager) { + if (!instanceContainer.tileMapCollisionMaskManager) { // Create the shared manager if necessary. - runtimeScene.tileMapCollisionMaskManager = new TileMapRuntimeManager( - runtimeScene + instanceContainer.tileMapCollisionMaskManager = new TileMapRuntimeManager( + instanceContainer ); } - return runtimeScene.tileMapCollisionMaskManager; + return instanceContainer.tileMapCollisionMaskManager; } /** @@ -109,7 +109,7 @@ namespace gdjs { tileSetJsonResourceName: string, callback: (tiledMap: TileMapHelper.TiledMap | null) => void ): void { - this._runtimeScene + this._instanceContainer .getGame() .getJsonManager() .loadJson(tileMapJsonResourceName, (error, tileMapJsonData) => { @@ -123,7 +123,7 @@ namespace gdjs { } const tiledMap = tileMapJsonData as TileMapHelper.TiledMap; if (tileSetJsonResourceName) { - this._runtimeScene + this._instanceContainer .getGame() .getJsonManager() .loadJson(tileSetJsonResourceName, (error, tileSetJsonData) => { diff --git a/Extensions/TileMap/collision/TileMapCollisionMaskRenderer.ts b/Extensions/TileMap/collision/TileMapCollisionMaskRenderer.ts index 5e93b59fb4..d53d508afa 100644 --- a/Extensions/TileMap/collision/TileMapCollisionMaskRenderer.ts +++ b/Extensions/TileMap/collision/TileMapCollisionMaskRenderer.ts @@ -12,11 +12,11 @@ namespace gdjs { constructor( runtimeObject: gdjs.TileMapCollisionMaskRuntimeObject, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { this._object = runtimeObject; this._graphics = new PIXI.Graphics(); - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject(this._graphics, runtimeObject.getZOrder()); diff --git a/Extensions/TileMap/tilemapcollisionmaskruntimeobject.ts b/Extensions/TileMap/tilemapcollisionmaskruntimeobject.ts index e587fdb861..da8ad0b17e 100644 --- a/Extensions/TileMap/tilemapcollisionmaskruntimeobject.ts +++ b/Extensions/TileMap/tilemapcollisionmaskruntimeobject.ts @@ -42,8 +42,8 @@ namespace gdjs { */ private _transformationIsUpToDate: boolean = false; - constructor(runtimeScene: gdjs.RuntimeScene, objectData) { - super(runtimeScene, objectData); + constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) { + super(instanceContainer, objectData); this._tilemapJsonFile = objectData.content.tilemapJsonFile; this._tilesetJsonFile = objectData.content.tilesetJsonFile; this._collisionMaskTag = objectData.content.collisionMaskTag; @@ -58,7 +58,7 @@ namespace gdjs { this._outlineOpacity = objectData.content.outlineOpacity; this._outlineSize = objectData.content.outlineSize; this._tileMapManager = gdjs.TileMap.TileMapRuntimeManager.getManager( - runtimeScene + instanceContainer ); // The actual size is set when the tile map file is loaded. @@ -80,7 +80,7 @@ namespace gdjs { this._renderer = new gdjs.TileMap.TileMapCollisionMaskRenderer( this, - runtimeScene + instanceContainer ); this._updateTileMap(); @@ -88,8 +88,8 @@ namespace gdjs { this.onCreated(); } - updatePreRender(runtimeScene: gdjs.RuntimeScene) { - super.updatePreRender(runtimeScene); + updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer) { + super.updatePreRender(instanceContainer); if (this._debugMode && this.hitBoxesDirty) { this.updateHitBoxes(); @@ -472,8 +472,8 @@ namespace gdjs { } this._scaleX = width / this._collisionTileMap.getWidth(); this._width = width; - this.hitBoxesDirty = true; this._transformationIsUpToDate = false; + this.invalidateHitboxes(); } /** @@ -487,8 +487,8 @@ namespace gdjs { } this._scaleY = height / this._collisionTileMap.getHeight(); this._height = height; - this.hitBoxesDirty = true; this._transformationIsUpToDate = false; + this.invalidateHitboxes(); } /** @@ -515,8 +515,8 @@ namespace gdjs { } this._scaleX = scaleX; this._width = scaleX * this._collisionTileMap.getWidth(); - this.hitBoxesDirty = true; this._transformationIsUpToDate = false; + this.invalidateHitboxes(); } /** @@ -533,8 +533,8 @@ namespace gdjs { } this._scaleY = scaleY; this._height = scaleY * this._collisionTileMap.getHeight(); - this.hitBoxesDirty = true; this._transformationIsUpToDate = false; + this.invalidateHitboxes(); } getWidth(): float { diff --git a/Extensions/TileMap/tilemapruntimeobject-pixi-renderer.ts b/Extensions/TileMap/tilemapruntimeobject-pixi-renderer.ts index 7121c97662..bc1cb8efbf 100644 --- a/Extensions/TileMap/tilemapruntimeobject-pixi-renderer.ts +++ b/Extensions/TileMap/tilemapruntimeobject-pixi-renderer.ts @@ -16,11 +16,11 @@ namespace gdjs { /** * @param runtimeObject The object to render - * @param runtimeScene The gdjs.RuntimeScene in which the object is + * @param instanceContainer The gdjs.RuntimeScene in which the object is */ constructor( runtimeObject: gdjs.TileMapRuntimeObject, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { this._object = runtimeObject; @@ -31,7 +31,7 @@ namespace gdjs { this._pixiObject = new PIXI.tilemap.CompositeTilemap(); this._pixiObject.tileAnim = [0, 0]; - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject(this._pixiObject, runtimeObject.getZOrder()); @@ -44,7 +44,7 @@ namespace gdjs { return this._pixiObject; } - incrementAnimationFrameX(runtimeScene: gdjs.RuntimeScene) { + incrementAnimationFrameX(instanceContainer: gdjs.RuntimeInstanceContainer) { this._pixiObject.tileAnim[0] += 1; } diff --git a/Extensions/TileMap/tilemapruntimeobject.ts b/Extensions/TileMap/tilemapruntimeobject.ts index adfe3dc52d..cf4a369197 100644 --- a/Extensions/TileMap/tilemapruntimeobject.ts +++ b/Extensions/TileMap/tilemapruntimeobject.ts @@ -19,8 +19,8 @@ namespace gdjs { _tileMapManager: gdjs.TileMap.TileMapRuntimeManager; _renderer: gdjs.TileMapRuntimeObjectPixiRenderer; - constructor(runtimeScene: gdjs.RuntimeScene, objectData) { - super(runtimeScene, objectData); + constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) { + super(instanceContainer, objectData); this._opacity = objectData.content.opacity; this._tilemapJsonFile = objectData.content.tilemapJsonFile; this._tilesetJsonFile = objectData.content.tilesetJsonFile; @@ -30,11 +30,11 @@ namespace gdjs { this._animationSpeedScale = objectData.content.animationSpeedScale; this._animationFps = objectData.content.animationFps; this._tileMapManager = gdjs.TileMap.TileMapRuntimeManager.getManager( - runtimeScene + instanceContainer ); this._renderer = new gdjs.TileMapRuntimeObjectRenderer( this, - runtimeScene + instanceContainer ); this._updateTileMap(); @@ -46,14 +46,14 @@ namespace gdjs { return this._renderer.getRendererObject(); } - update(runtimeScene: gdjs.RuntimeScene): void { + update(instanceContainer: gdjs.RuntimeInstanceContainer): void { if (this._animationSpeedScale <= 0 || this._animationFps === 0) { return; } - const elapsedTime = this.getElapsedTime(runtimeScene) / 1000; + const elapsedTime = this.getElapsedTime() / 1000; this._frameElapsedTime += elapsedTime * this._animationSpeedScale; while (this._frameElapsedTime > 1 / this._animationFps) { - this._renderer.incrementAnimationFrameX(runtimeScene); + this._renderer.incrementAnimationFrameX(instanceContainer); this._frameElapsedTime -= 1 / this._animationFps; } } @@ -124,7 +124,7 @@ namespace gdjs { } this._tileMapManager.getOrLoadTextureCache( (textureName) => - (this._runtimeScene + (this.getInstanceContainer() .getGame() .getImageManager() .getPIXITexture(textureName) as unknown) as PIXI.BaseTexture< @@ -221,7 +221,7 @@ namespace gdjs { if (this.getWidth() === width) return; this._renderer.setWidth(width); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -233,7 +233,7 @@ namespace gdjs { if (this.getHeight() === height) return; this._renderer.setHeight(height); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -258,7 +258,7 @@ namespace gdjs { if (this.getScaleX() === scaleX) return; this._renderer.setScaleX(scaleX); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -273,7 +273,7 @@ namespace gdjs { if (this.getScaleY() === scaleY) return; this._renderer.setScaleY(scaleY); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } setX(x: float): void { diff --git a/Extensions/TiledSpriteObject/tiledspriteruntimeobject-pixi-renderer.ts b/Extensions/TiledSpriteObject/tiledspriteruntimeobject-pixi-renderer.ts index 92bb8794ec..98972272c2 100644 --- a/Extensions/TiledSpriteObject/tiledspriteruntimeobject-pixi-renderer.ts +++ b/Extensions/TiledSpriteObject/tiledspriteruntimeobject-pixi-renderer.ts @@ -7,17 +7,17 @@ namespace gdjs { constructor( runtimeObject: gdjs.TiledSpriteRuntimeObject, - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, textureName: string ) { this._object = runtimeObject; - const texture = runtimeScene + const texture = instanceContainer .getGame() .getImageManager() .getPIXITexture(textureName); this._tiledSprite = new PIXI.TilingSprite(texture, 1024, 1024); - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject(this._tiledSprite, runtimeObject.getZOrder()); @@ -42,8 +42,11 @@ namespace gdjs { this._object.y + this._tiledSprite.height / 2; } - setTexture(textureName, runtimeScene): void { - const texture = runtimeScene + setTexture( + textureName: string, + instanceContainer: RuntimeInstanceContainer + ): void { + const texture = instanceContainer .getGame() .getImageManager() .getPIXITexture(textureName); diff --git a/Extensions/TiledSpriteObject/tiledspriteruntimeobject.ts b/Extensions/TiledSpriteObject/tiledspriteruntimeobject.ts index 378bcb53ac..2b13298349 100644 --- a/Extensions/TiledSpriteObject/tiledspriteruntimeobject.ts +++ b/Extensions/TiledSpriteObject/tiledspriteruntimeobject.ts @@ -30,17 +30,17 @@ namespace gdjs { _renderer: gdjs.TiledSpriteRuntimeObjectRenderer; /** - * @param runtimeScene The scene the object belongs to. + * @param instanceContainer The container the object belongs to. * @param tiledSpriteObjectData The initial properties of the object */ constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, tiledSpriteObjectData: TiledSpriteObjectData ) { - super(runtimeScene, tiledSpriteObjectData); + super(instanceContainer, tiledSpriteObjectData); this._renderer = new gdjs.TiledSpriteRuntimeObjectRenderer( this, - runtimeScene, + instanceContainer, tiledSpriteObjectData.texture ); this._width = 0; @@ -54,7 +54,7 @@ namespace gdjs { updateFromObjectData(oldObjectData, newObjectData): boolean { if (oldObjectData.texture !== newObjectData.texture) { - this.setTexture(newObjectData.texture, this._runtimeScene); + this.setTexture(newObjectData.texture, this.getRuntimeScene()); } if (oldObjectData.width !== newObjectData.width) { this.setWidth(newObjectData.width); @@ -69,8 +69,8 @@ namespace gdjs { return this._renderer.getRendererObject(); } - onDestroyFromScene(runtimeScene): void { - super.onDestroyFromScene(runtimeScene); + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { + super.onDestroyFromScene(instanceContainer); if ((this._renderer as any).onDestroy) { (this._renderer as any).onDestroy(); } @@ -106,11 +106,14 @@ namespace gdjs { /** * Assign a new texture to the Tiled Sprite object. - * @param textureName The name of the image texture ressource. - * @param runtimeScene The scene in which the texture is used. + * @param textureName The name of the image texture resource. + * @param instanceContainer The container in which the texture is used. */ - setTexture(textureName: string, runtimeScene: gdjs.RuntimeScene): void { - this._renderer.setTexture(textureName, runtimeScene); + setTexture( + textureName: string, + instanceContainer: gdjs.RuntimeInstanceContainer + ): void { + this._renderer.setTexture(textureName, instanceContainer); } /** @@ -147,7 +150,7 @@ namespace gdjs { this._width = width; this._renderer.setWidth(width); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -159,7 +162,7 @@ namespace gdjs { this._height = height; this._renderer.setHeight(height); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** diff --git a/Extensions/TopDownMovementBehavior/topdownmovementruntimebehavior.ts b/Extensions/TopDownMovementBehavior/topdownmovementruntimebehavior.ts index f8766d3e08..27748baf18 100644 --- a/Extensions/TopDownMovementBehavior/topdownmovementruntimebehavior.ts +++ b/Extensions/TopDownMovementBehavior/topdownmovementruntimebehavior.ts @@ -48,11 +48,11 @@ namespace gdjs { private _temporaryPointForTransformations: FloatPoint = [0, 0]; constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData, owner: gdjs.RuntimeObject ) { - super(runtimeScene, behaviorData, owner); + super(instanceContainer, behaviorData, owner); this._allowDiagonals = behaviorData.allowDiagonals; this._acceleration = behaviorData.acceleration; this._deceleration = behaviorData.deceleration; @@ -227,7 +227,7 @@ namespace gdjs { return this._movementAngleOffset; } - doStepPreEvents(runtimeScene: gdjs.RuntimeScene) { + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { const LEFTKEY = 37; const UPKEY = 38; const RIGHTKEY = 39; @@ -237,21 +237,21 @@ namespace gdjs { // @ts-ignore this._leftKey |= !this._ignoreDefaultControls && - runtimeScene.getGame().getInputManager().isKeyPressed(LEFTKEY); + instanceContainer.getGame().getInputManager().isKeyPressed(LEFTKEY); // @ts-ignore this._rightKey |= !this._ignoreDefaultControls && - runtimeScene.getGame().getInputManager().isKeyPressed(RIGHTKEY); + instanceContainer.getGame().getInputManager().isKeyPressed(RIGHTKEY); // @ts-ignore this._downKey |= !this._ignoreDefaultControls && - runtimeScene.getGame().getInputManager().isKeyPressed(DOWNKEY); + instanceContainer.getGame().getInputManager().isKeyPressed(DOWNKEY); // @ts-ignore this._upKey |= !this._ignoreDefaultControls && - runtimeScene.getGame().getInputManager().isKeyPressed(UPKEY); + instanceContainer.getGame().getInputManager().isKeyPressed(UPKEY); - const elapsedTime = this.owner.getElapsedTime(runtimeScene); + const elapsedTime = this.owner.getElapsedTime(); if (!this._leftKey) { this._leftKeyPressedDuration = 0; @@ -330,7 +330,7 @@ namespace gdjs { } const object = this.owner; - const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000; + const timeDelta = this.owner.getElapsedTime() / 1000; const previousVelocityX = this._xVelocity; const previousVelocityY = this._yVelocity; this._wasStickUsed = false; @@ -443,8 +443,7 @@ namespace gdjs { if (this._rotateObject) { object.rotateTowardAngle( directionInDeg + this._angleOffset, - this._angularSpeed, - runtimeScene + this._angularSpeed ); } } diff --git a/Extensions/TweenBehavior/tweenruntimebehavior.ts b/Extensions/TweenBehavior/tweenruntimebehavior.ts index 0c8093e7f7..928822be14 100644 --- a/Extensions/TweenBehavior/tweenruntimebehavior.ts +++ b/Extensions/TweenBehavior/tweenruntimebehavior.ts @@ -76,23 +76,25 @@ namespace gdjs { ]; } + // TODO EBO Rewrite this behavior to use standard method to step. + // This could also fix layer time scale that seems to be ignored. export class TweenRuntimeBehavior extends gdjs.RuntimeBehavior { private _tweens: Record = {}; private _runtimeScene: gdjs.RuntimeScene; private _isActive: boolean = true; /** - * @param runtimeScene The runtime scene the behavior belongs to. + * @param instanceContainer The instance container the behavior belongs to. * @param behaviorData The data to initialize the behavior * @param owner The runtime object the behavior belongs to. */ constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData: BehaviorData, owner: gdjs.RuntimeObject ) { - super(runtimeScene, behaviorData, owner); - this._runtimeScene = runtimeScene; + super(instanceContainer, behaviorData, owner); + this._runtimeScene = instanceContainer.getScene(); } updateFromBehaviorData( diff --git a/Extensions/Video/videoruntimeobject-pixi-renderer.ts b/Extensions/Video/videoruntimeobject-pixi-renderer.ts index b67757ba66..b71fb05eac 100644 --- a/Extensions/Video/videoruntimeobject-pixi-renderer.ts +++ b/Extensions/Video/videoruntimeobject-pixi-renderer.ts @@ -7,7 +7,7 @@ namespace gdjs { * The PIXI.js renderer for the VideoRuntimeObject. */ export class VideoRuntimeObjectPixiRenderer { - _object: any; + _object: gdjs.VideoRuntimeObject; // Load (or reset) the video _pixiObject: any; @@ -15,15 +15,15 @@ namespace gdjs { /** * @param runtimeObject The object to render - * @param runtimeScene The gdjs.RuntimeScene in which the object is + * @param instanceContainer The gdjs.RuntimeScene in which the object is */ constructor( runtimeObject: gdjs.VideoRuntimeObject, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { this._object = runtimeObject; this._pixiObject = new PIXI.Sprite( - runtimeScene + instanceContainer .getGame() .getImageManager() .getPIXIVideoTexture(this._object._videoResource) @@ -36,7 +36,7 @@ namespace gdjs { this._pixiObject._texture.baseTexture.resource.source.autoload = true; // Will be set to true when video texture is loaded. - runtimeScene + instanceContainer .getLayer('') .getRenderer() .addRendererObject(this._pixiObject, runtimeObject.getZOrder()); diff --git a/Extensions/Video/videoruntimeobject.ts b/Extensions/Video/videoruntimeobject.ts index ff2b30f059..3d4f09561b 100644 --- a/Extensions/Video/videoruntimeobject.ts +++ b/Extensions/Video/videoruntimeobject.ts @@ -37,19 +37,22 @@ namespace gdjs { _playbackSpeed: any; /** - * @param runtimeScene The scene the object belongs to. + * @param instanceContainer The scene the object belongs to. * @param videoObjectData The data defining the object */ constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, videoObjectData: VideoObjectData ) { - super(runtimeScene, videoObjectData); + super(instanceContainer, videoObjectData); this._opacity = videoObjectData.content.opacity; this._loop = videoObjectData.content.loop; this._volume = videoObjectData.content.volume; this._videoResource = videoObjectData.content.videoResource; - this._renderer = new gdjs.VideoRuntimeObjectRenderer(this, runtimeScene); + this._renderer = new gdjs.VideoRuntimeObjectRenderer( + this, + instanceContainer + ); // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. this.onCreated(); @@ -92,12 +95,12 @@ namespace gdjs { } } - onDestroyFromScene(runtimeScene): void { - super.onDestroyFromScene(runtimeScene); + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { + super.onDestroyFromScene(instanceContainer); this._renderer.onDestroy(); } - update(runtimeScene): void { + update(instanceContainer: gdjs.RuntimeInstanceContainer): void { this._renderer.ensureUpToDate(); } @@ -153,7 +156,7 @@ namespace gdjs { if (this._renderer.getWidth() === width) return; this._renderer.setWidth(width); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -164,7 +167,7 @@ namespace gdjs { if (this._renderer.getHeight() === height) return; this._renderer.setHeight(height); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** diff --git a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp index 9f6a2419f7..4c1afc4247 100644 --- a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp @@ -19,6 +19,7 @@ #include "GDCore/IDE/SceneNameMangler.h" #include "GDCore/Project/Behavior.h" #include "GDCore/Project/EventsBasedBehavior.h" +#include "GDCore/Project/EventsBasedObject.h" #include "GDCore/Project/EventsFunction.h" #include "GDCore/Project/ExternalEvents.h" #include "GDCore/Project/Layout.h" @@ -38,6 +39,7 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode( gd::String functionArgumentsCode, gd::String functionPreEventsCode, const gd::EventsList& events, + gd::String functionPostEventsCode, gd::String functionReturnCode) { // Prepare the global context unsigned int maxDepthLevelReached = 0; @@ -80,6 +82,7 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode( functionPreEventsCode + "\n" + globalObjectListsReset + "\n" + wholeEventsCode + "\n" + + functionPostEventsCode + "\n" + functionReturnCode + "\n" + "}\n"; // clang-format on @@ -103,6 +106,7 @@ gd::String EventsCodeGenerator::GenerateLayoutCode( "runtimeScene", "runtimeScene.getOnceTriggers().startNewFrame();\n", scene.GetEvents(), + "", "return;\n"); includeFiles.insert(codeGenerator.GetIncludeFiles().begin(), @@ -129,10 +133,11 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode( codeGenerator, codeGenerator.GetCodeNamespaceAccessor() + "func", codeGenerator.GenerateEventsFunctionParameterDeclarationsList( - eventsFunction.GetParameters(), false), + eventsFunction.GetParameters(), 0, true), codeGenerator.GenerateFreeEventsFunctionContext( eventsFunction.GetParameters(), "runtimeScene.getOnceTriggers()"), eventsFunction.GetEvents(), + "", codeGenerator.GenerateEventsFunctionReturn(eventsFunction)); includeFiles.insert(codeGenerator.GetIncludeFiles().begin(), @@ -191,9 +196,83 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode( codeGenerator, fullyQualifiedFunctionName, codeGenerator.GenerateEventsFunctionParameterDeclarationsList( - eventsFunction.GetParameters(), true), + eventsFunction.GetParameters(), 2, false), fullPreludeCode, eventsFunction.GetEvents(), + "", + codeGenerator.GenerateEventsFunctionReturn(eventsFunction)); + + includeFiles.insert(codeGenerator.GetIncludeFiles().begin(), + codeGenerator.GetIncludeFiles().end()); + return output; +} + +gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode( + gd::Project& project, + const gd::EventsBasedObject& eventsBasedObject, + const gd::EventsFunction& eventsFunction, + const gd::String& codeNamespace, + const gd::String& fullyQualifiedFunctionName, + const gd::String& onceTriggersVariable, + const gd::String& preludeCode, + const gd::String& endingCode, + std::set& includeFiles, + bool compilationForRuntime) { + gd::ObjectsContainer globalObjectsAndGroups; + gd::ObjectsContainer objectsAndGroups; + gd::EventsFunctionTools::ObjectEventsFunctionToObjectsContainer( + project, + eventsBasedObject, + eventsFunction, + globalObjectsAndGroups, + objectsAndGroups); + + EventsCodeGenerator codeGenerator(globalObjectsAndGroups, objectsAndGroups); + codeGenerator.SetCodeNamespace(codeNamespace); + codeGenerator.SetGenerateCodeForRuntime(compilationForRuntime); + + // Generate the code setting up the context of the function. + gd::String fullPreludeCode = + preludeCode + "\n" + "var that = this;\n" + + // runtimeScene is supposed to be always accessible, read + // it from the object + "var runtimeScene = this._instanceContainer;\n" + + // By convention of Object Events Function, the object is accessible + // as a parameter called "Object", and thisObjectList is an array + // containing it (for faster access, without having to go through the + // hashmap). + "var thisObjectList = [this];\n" + + "var Object = Hashtable.newFrom({Object: thisObjectList});\n"; + + // Add child-objects + for (auto &childObject : eventsBasedObject.GetObjects()) { + // child-object are never picked because they are not parameters. + const auto &childName = ManObjListName(childObject->GetName()); + fullPreludeCode += + "var this" + childName + "List = [...runtimeScene.getObjects(" + + ConvertToStringExplicit(childObject->GetName()) + ")];\n" + + "var " + childName + " = Hashtable.newFrom({" + + ConvertToStringExplicit(childObject->GetName()) + + ": this" + childName + "List});\n"; + } + + fullPreludeCode += codeGenerator.GenerateObjectEventsFunctionContext( + eventsBasedObject, + eventsFunction.GetParameters(), + onceTriggersVariable, + // Pass the names of the parameters considered as the current + // object and behavior parameters: + "Object"); + + gd::String output = GenerateEventsListCompleteFunctionCode( + codeGenerator, + fullyQualifiedFunctionName, + codeGenerator.GenerateEventsFunctionParameterDeclarationsList( + // TODO EBO use constants for firstParameterIndex + eventsFunction.GetParameters(), 1, false), + fullPreludeCode, + eventsFunction.GetEvents(), + endingCode, codeGenerator.GenerateEventsFunctionReturn(eventsFunction)); includeFiles.insert(codeGenerator.GetIncludeFiles().begin(), @@ -203,11 +282,12 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode( gd::String EventsCodeGenerator::GenerateEventsFunctionParameterDeclarationsList( const vector& parameters, - bool isBehaviorEventsFunction) { - gd::String declaration = isBehaviorEventsFunction ? "" : "runtimeScene"; + int firstParameterIndex, + bool addsSceneParameter) { + gd::String declaration = addsSceneParameter ? "runtimeScene" : ""; for (size_t i = 0; i < parameters.size(); ++i) { const auto& parameter = parameters[i]; - if (isBehaviorEventsFunction && (i == 0 || i == 1)) { + if (i < firstParameterIndex) { // By convention, the first two arguments of a behavior events function // are the object and the behavior, which are not passed to the called // function in the generated JS code. @@ -294,6 +374,44 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext( thisBehaviorName); } +gd::String EventsCodeGenerator::GenerateObjectEventsFunctionContext( + const gd::EventsBasedObject& eventsBasedObject, + const vector& parameters, + const gd::String& onceTriggersVariable, + const gd::String& thisObjectName) { + // See the comment at the start of the GenerateEventsFunctionContext function + + gd::String objectsGettersMap; + gd::String objectArraysMap; + gd::String behaviorNamesMap; + + // If we have an object considered as the current object ("this") (usually + // called Object in behavior events function), generate a slightly more + // optimized getter for it (bypassing "Object" hashmap, and directly return + // the array containing it). + if (!thisObjectName.empty()) { + objectsGettersMap += + ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName + "\n"; + objectArraysMap += + ConvertToStringExplicit(thisObjectName) + ": thisObjectList\n"; + + // Add child-objects + for (auto &childObject : eventsBasedObject.GetObjects()) { + const auto &childName = ManObjListName(childObject->GetName()); + // child-object are never picked because they are not parameters. + objectsGettersMap += ", " + ConvertToStringExplicit(childObject->GetName()) + ": " + childName + "\n"; + objectArraysMap += ", " + ConvertToStringExplicit(childObject->GetName()) + ": this" + childName + "List\n"; + } + } + + return GenerateEventsFunctionContext(parameters, + onceTriggersVariable, + objectsGettersMap, + objectArraysMap, + behaviorNamesMap, + thisObjectName); +} + gd::String EventsCodeGenerator::GenerateEventsFunctionContext( const vector& parameters, const gd::String& onceTriggersVariable, @@ -379,7 +497,8 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext( // can be different between the parameter name vs the actual behavior // name passed as argument). " getBehaviorName: function(behaviorName) {\n" + - " return eventsFunctionContext._behaviorNamesMap[behaviorName];\n" + // TODO EBO Handle behavior name collision between parameters and children + " return eventsFunctionContext._behaviorNamesMap[behaviorName] || behaviorName;\n" " },\n" + // Creator function that will be used to create new objects. We // need to check if the function was given the context of the calling diff --git a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h index 7b90226611..38eefd35f9 100644 --- a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h +++ b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h @@ -16,6 +16,7 @@ namespace gd { class ObjectsContainer; class EventsFunction; class EventsBasedBehavior; +class EventsBasedObject; class ObjectMetadata; class BehaviorMetadata; class InstructionMetadata; @@ -76,10 +77,13 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator { * \param project Project used. * \param eventsFunction The events function to be compiled. * \param codeNamespace Where to store the context used by the function. - * \param includeFiles Will be filled with the necessary include files. + * \param fullyQualifiedFunctionName The function name with its namespace. * \param onceTriggersVariable The code to access the variable holding - * OnceTriggers. \param preludeCode The code to run just before the events - * generated code. \param compilationForRuntime Set this to true if the code + * OnceTriggers. + * \param preludeCode The code to run just before the events + * generated code. + * \param includeFiles Will be filled with the necessary include files. + * \param compilationForRuntime Set this to true if the code * is generated for runtime. * * \return JavaScript code @@ -95,6 +99,39 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator { std::set& includeFiles, bool compilationForRuntime = false); + /** + * Generate JavaScript for executing events of a events based object + * function. + * + * \param project Project used. + * \param eventsBasedObject The object that contains the function to be compiled. + * \param eventsFunction The events function to be compiled. + * \param codeNamespace Where to store the context used by the function. + * \param fullyQualifiedFunctionName The function name with its namespace. + * \param onceTriggersVariable The code to access the variable holding + * OnceTriggers. + * \param preludeCode The code to run right before the events + * generated code. + * \param endingCode The code to run right after the events + * generated code. + * \param includeFiles Will be filled with the necessary include files. + * \param compilationForRuntime Set this to true if the code + * is generated for runtime. + * + * \return JavaScript code + */ + static gd::String GenerateObjectEventsFunctionCode( + gd::Project& project, + const gd::EventsBasedObject& eventsBasedObject, + const gd::EventsFunction& eventsFunction, + const gd::String& codeNamespace, + const gd::String& fullyQualifiedFunctionName, + const gd::String& onceTriggersVariable, + const gd::String& preludeCode, + const gd::String& endingCode, + std::set& includeFiles, + bool compilationForRuntime = false); + /** * \brief Generate code for executing an event list * \note To reduce the stress on JS engines, the code is generated inside @@ -296,6 +333,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator { gd::String functionArgumentsCode, gd::String functionPreEventsCode, const gd::EventsList& events, + gd::String functionPostEventsCode, gd::String functionReturnCode); /** @@ -324,7 +362,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator { */ gd::String GenerateEventsFunctionParameterDeclarationsList( const std::vector& parameters, - bool isBehaviorEventsFunction); + int firstParameterIndex, + bool addsSceneParameter); /** * \brief Generate the "eventsFunctionContext" object that allow a free @@ -347,6 +386,17 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator { const gd::String& thisObjectName, const gd::String& thisBehaviorName); + /** + * \brief Generate the "eventsFunctionContext" object that allow an object + * function to provides access objects, object creation and access to + * arguments from the rest of the events. + */ + gd::String GenerateObjectEventsFunctionContext( + const gd::EventsBasedObject& eventsBasedObject, + const std::vector& parameters, + const gd::String& onceTriggersVariable, + const gd::String& thisObjectName); + gd::String GenerateEventsFunctionReturn( const gd::EventsFunction& eventFunction); diff --git a/GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.cpp b/GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.cpp new file mode 100644 index 0000000000..6dd806cb69 --- /dev/null +++ b/GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.cpp @@ -0,0 +1,250 @@ +/* + * GDevelop JS Platform + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "ObjectCodeGenerator.h" + +#include "EventsCodeGenerator.h" + +namespace gdjs { + +gd::String ObjectCodeGenerator::onCreatedFunctionName = + "onCreated"; + +gd::String ObjectCodeGenerator::doStepPreEventsFunctionName = + "doStepPreEvents"; + +gd::String ObjectCodeGenerator::GenerateRuntimeObjectCompleteCode( + const gd::String& extensionName, + const gd::EventsBasedObject& eventsBasedObject, + const gd::String& codeNamespace, + const std::map& objectMethodMangledNames, + std::set& includeFiles, + bool compilationForRuntime) { + auto& eventsFunctionsVector = + eventsBasedObject.GetEventsFunctions().GetInternalVector(); + + return GenerateRuntimeObjectTemplateCode( + extensionName, + eventsBasedObject, + codeNamespace, + [&]() { + gd::String runtimeObjectDataInitializationCode; + for (auto& property : + eventsBasedObject.GetPropertyDescriptors().GetInternalVector()) { + runtimeObjectDataInitializationCode += + property->IsHidden() + ? GenerateInitializePropertyFromDefaultValueCode(*property) + : GenerateInitializePropertyFromDataCode(*property); + } + + return runtimeObjectDataInitializationCode; + }, + [&]() { + gd::String runtimeObjectPropertyMethodsCode; + for (auto& property : + eventsBasedObject.GetPropertyDescriptors().GetInternalVector()) { + runtimeObjectPropertyMethodsCode += + GenerateRuntimeObjectPropertyTemplateCode( + eventsBasedObject, *property); + } + + return runtimeObjectPropertyMethodsCode; + }, + // TODO: Update code generation to be able to generate methods (which would allow + // for a cleaner output, not having to add methods to the prototype). + [&]() { + gd::String runtimeObjectMethodsCode; + for (auto& eventsFunction : eventsFunctionsVector) { + const gd::String& functionName = + objectMethodMangledNames.find(eventsFunction->GetName()) != + objectMethodMangledNames.end() + ? objectMethodMangledNames.find(eventsFunction->GetName()) + ->second + : "UNKNOWN_FUNCTION_fix_objectMethodMangledNames_please"; + gd::String methodCodeNamespace = + codeNamespace + "." + eventsBasedObject.GetName() + + ".prototype." + functionName + "Context"; + gd::String methodFullyQualifiedName = codeNamespace + "." + + eventsBasedObject.GetName() + + ".prototype." + functionName; + runtimeObjectMethodsCode += + EventsCodeGenerator::GenerateObjectEventsFunctionCode( + project, + eventsBasedObject, + *eventsFunction, + methodCodeNamespace, + methodFullyQualifiedName, + "that._onceTriggers", + functionName == doStepPreEventsFunctionName + ? GenerateDoStepPreEventsPreludeCode() + : "", + functionName == onCreatedFunctionName + ? "gdjs.CustomRuntimeObject.prototype.onCreated.call(this);\n" + : "", + includeFiles, + compilationForRuntime); + } + + bool hasDoStepPreEventsFunction = + eventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed( + doStepPreEventsFunctionName); + if (!hasDoStepPreEventsFunction) { + runtimeObjectMethodsCode += + GenerateDefaultDoStepPreEventsFunctionCode(eventsBasedObject, + codeNamespace); + } + + return runtimeObjectMethodsCode; + }, + [&]() { + gd::String updateFromObjectCode; + updateFromObjectCode += "super.updateFromObjectData(oldObjectData, newObjectData);"; + for (auto& property : + eventsBasedObject.GetPropertyDescriptors().GetInternalVector()) { + updateFromObjectCode += + GenerateUpdatePropertyFromObjectDataCode( + eventsBasedObject, *property); + } + + return updateFromObjectCode; + }); +} + +gd::String ObjectCodeGenerator::GenerateRuntimeObjectTemplateCode( + const gd::String& extensionName, + const gd::EventsBasedObject& eventsBasedObject, + const gd::String& codeNamespace, + std::function generateInitializePropertiesCode, + std::function generatePropertiesCode, + std::function generateMethodsCode, + std::function generateUpdateFromObjectDataCode) { + return gd::String(R"jscode_template( +CODE_NAMESPACE = CODE_NAMESPACE || {}; + +/** + * Object generated from OBJECT_FULL_NAME + */ +CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME = class RUNTIME_OBJECT_CLASSNAME extends gdjs.CustomRuntimeObject { + constructor(runtimeScene, objectData) { + super(runtimeScene, objectData); + this._runtimeScene = runtimeScene; + + this._onceTriggers = new gdjs.OnceTriggers(); + this._behaviorData = {}; + INITIALIZE_PROPERTIES_CODE + } + + // Hot-reload: + updateFromObjectData(oldObjectData, newObjectData) { + UPDATE_FROM_OBJECT_DATA_CODE + + this.onHotReloading(this.getInstanceContainer()); + return true; + } + + // Properties: + PROPERTIES_CODE +} + +// Methods: +METHODS_CODE + +gdjs.registerObject("EXTENSION_NAME::OBJECT_NAME", CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME); +)jscode_template") + .FindAndReplace("EXTENSION_NAME", extensionName) + .FindAndReplace("OBJECT_NAME", eventsBasedObject.GetName()) + .FindAndReplace("OBJECT_FULL_NAME", eventsBasedObject.GetFullName()) + .FindAndReplace("RUNTIME_OBJECT_CLASSNAME", + eventsBasedObject.GetName()) + .FindAndReplace("CODE_NAMESPACE", codeNamespace) + .FindAndReplace("INITIALIZE_PROPERTIES_CODE", + generateInitializePropertiesCode()) + .FindAndReplace("UPDATE_FROM_OBJECT_DATA_CODE", generateUpdateFromObjectDataCode()) + .FindAndReplace("PROPERTIES_CODE", generatePropertiesCode()) + .FindAndReplace("METHODS_CODE", generateMethodsCode()); + ; +} +// TODO these 2 methods are probably not needed if the properties are merged by GDJS. +gd::String ObjectCodeGenerator::GenerateInitializePropertyFromDataCode( + const gd::NamedPropertyDescriptor& property) { + return gd::String(R"jscode_template( + this._objectData.content.PROPERTY_NAME = objectData.content.PROPERTY_NAME !== undefined ? objectData.content.PROPERTY_NAME : DEFAULT_VALUE;)jscode_template") + .FindAndReplace("PROPERTY_NAME", property.GetName()) + .FindAndReplace("DEFAULT_VALUE", GeneratePropertyValueCode(property)); +} +gd::String +ObjectCodeGenerator::GenerateInitializePropertyFromDefaultValueCode( + const gd::NamedPropertyDescriptor& property) { + return gd::String(R"jscode_template( + this._objectData.content.PROPERTY_NAME = DEFAULT_VALUE;)jscode_template") + .FindAndReplace("PROPERTY_NAME", property.GetName()) + .FindAndReplace("DEFAULT_VALUE", GeneratePropertyValueCode(property)); +} + +gd::String ObjectCodeGenerator::GenerateRuntimeObjectPropertyTemplateCode( + const gd::EventsBasedObject& eventsBasedObject, + const gd::NamedPropertyDescriptor& property) { + return gd::String(R"jscode_template( + GETTER_NAME() { + return this._objectData.content.PROPERTY_NAME !== undefined ? this._objectData.content.PROPERTY_NAME : DEFAULT_VALUE; + } + SETTER_NAME(newValue) { + this._objectData.content.PROPERTY_NAME = newValue; + })jscode_template") + .FindAndReplace("PROPERTY_NAME", property.GetName()) + .FindAndReplace("GETTER_NAME", + GetObjectPropertyGetterName(property.GetName())) + .FindAndReplace("SETTER_NAME", + GetObjectPropertySetterName(property.GetName())) + .FindAndReplace("DEFAULT_VALUE", GeneratePropertyValueCode(property)) + .FindAndReplace("RUNTIME_OBJECT_CLASSNAME", + eventsBasedObject.GetName()); +} + +gd::String ObjectCodeGenerator::GenerateUpdatePropertyFromObjectDataCode( + const gd::EventsBasedObject& eventsBasedObject, + const gd::NamedPropertyDescriptor& property) { + return gd::String(R"jscode_template( + if (oldObjectData.content.PROPERTY_NAME !== newObjectData.content.PROPERTY_NAME) + this._objectData.content.PROPERTY_NAME = newObjectData.content.PROPERTY_NAME;)jscode_template") + .FindAndReplace("PROPERTY_NAME", property.GetName()); +} + +gd::String ObjectCodeGenerator::GeneratePropertyValueCode( + const gd::PropertyDescriptor& property) { + if (property.GetType() == "String" || + property.GetType() == "Choice" || + property.GetType() == "Color") { + return EventsCodeGenerator::ConvertToStringExplicit(property.GetValue()); + } else if (property.GetType() == "Number") { + return "Number(" + + EventsCodeGenerator::ConvertToStringExplicit(property.GetValue()) + + ") || 0"; + } else if (property.GetType() == "Boolean") { // TODO: Check if working + return property.GetValue() == "true" ? "true" : "false"; + } + + return "0 /* Error: property was of an unrecognized type */"; +} + +gd::String ObjectCodeGenerator::GenerateDefaultDoStepPreEventsFunctionCode( + const gd::EventsBasedObject& eventsBasedObject, + const gd::String& codeNamespace) { + return gd::String(R"jscode_template( +CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME.prototype.doStepPreEvents = function() { + PRELUDE_CODE +}; +)jscode_template") + .FindAndReplace("RUNTIME_OBJECT_CLASSNAME", + eventsBasedObject.GetName()) + .FindAndReplace("CODE_NAMESPACE", codeNamespace) + .FindAndReplace("PRELUDE_CODE", GenerateDoStepPreEventsPreludeCode()); +} + +gd::String ObjectCodeGenerator::GenerateDoStepPreEventsPreludeCode() { + return "this._onceTriggers.startNewFrame();"; +} + +} // namespace gdjs diff --git a/GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.h b/GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.h new file mode 100644 index 0000000000..298c518afa --- /dev/null +++ b/GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.h @@ -0,0 +1,95 @@ +/* + * GDevelop JS Platform + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#ifndef GDJS_OBJECTCODEGENERATOR_H +#define GDJS_OBJECTCODEGENERATOR_H +#include +#include +#include +#include + +#include "GDCore/Project/EventsBasedObject.h" +namespace gd { +class NamedPropertyDescriptor; +} + +namespace gdjs { + +/** + * \brief The class being responsible for generating JavaScript code for + * EventsBasedObject. + * + * See also gd::EventsCodeGenerator. + */ +class ObjectCodeGenerator { + public: + ObjectCodeGenerator(gd::Project& project_) : project(project_){}; + + /** + * \brief Generate the complete JS class (`gdjs.CustomRuntimeObject`) for the + * object. + */ + gd::String GenerateRuntimeObjectCompleteCode( + const gd::String& extensionName, + const gd::EventsBasedObject& eventsBasedObject, + const gd::String& codeNamespace, + const std::map& objectMethodMangledNames, + std::set& includeFiles, + bool compilationForRuntime = false); + + /** + * \brief Generate the name of the method to get the value of the property + * of a object. + */ + static gd::String GetObjectPropertyGetterName( + const gd::String& propertyName) { + return "_get" + propertyName; + } + + /** + * \brief Generate the name of the method to set the value of the property + * of a object. + */ + static gd::String GetObjectPropertySetterName( + const gd::String& propertyName) { + return "_set" + propertyName; + } + + private: + gd::String GenerateRuntimeObjectTemplateCode( + const gd::String& extensionName, + const gd::EventsBasedObject& eventsBasedObject, + const gd::String& codeNamespace, + std::function generateInitializePropertiesCode, + std::function generateMethodsCode, + std::function generatePropertiesCode, + std::function generateUpdateFromObjectDataCode); + gd::String GenerateRuntimeObjectPropertyTemplateCode( + const gd::EventsBasedObject& eventsBasedObject, + const gd::NamedPropertyDescriptor& property); + gd::String GenerateInitializePropertyFromDataCode( + const gd::NamedPropertyDescriptor& property); + gd::String GenerateInitializePropertyFromDefaultValueCode( + const gd::NamedPropertyDescriptor& property); + gd::String GeneratePropertyValueCode(const gd::PropertyDescriptor& property); + gd::String GenerateUpdatePropertyFromObjectDataCode( + const gd::EventsBasedObject& eventsBasedObject, + const gd::NamedPropertyDescriptor& property); + gd::String GenerateObjectOnDestroyToDeprecatedOnOwnerRemovedFromScene( + const gd::EventsBasedObject& eventsBasedObject, + const gd::String& codeNamespace); + gd::String GenerateDefaultDoStepPreEventsFunctionCode( + const gd::EventsBasedObject& eventsBasedObject, + const gd::String& codeNamespace); + gd::String GenerateDoStepPreEventsPreludeCode(); + + gd::Project& project; + + static gd::String onCreatedFunctionName; + static gd::String doStepPreEventsFunctionName; +}; + +} // namespace gdjs +#endif // GDJS_OBJECTCODEGENERATOR_H diff --git a/GDJS/GDJS/IDE/ExporterHelper.cpp b/GDJS/GDJS/IDE/ExporterHelper.cpp index 2430755a4d..996751accd 100644 --- a/GDJS/GDJS/IDE/ExporterHelper.cpp +++ b/GDJS/GDJS/IDE/ExporterHelper.cpp @@ -31,6 +31,8 @@ #include "GDCore/Project/ExternalLayout.h" #include "GDCore/Project/Layout.h" #include "GDCore/Project/Project.h" +#include "GDCore/Project/EventsBasedObject.h" +#include "GDCore/Project/EventsFunctionsExtension.h" #include "GDCore/Project/PropertyDescriptor.h" #include "GDCore/Project/SourceFile.h" #include "GDCore/Serialization/Serializer.h" @@ -560,10 +562,12 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers, InsertUnique(includesFiles, "polygon.js"); InsertUnique(includesFiles, "runtimeobject.js"); InsertUnique(includesFiles, "profiler.js"); + InsertUnique(includesFiles, "RuntimeInstanceContainer.js"); InsertUnique(includesFiles, "runtimescene.js"); InsertUnique(includesFiles, "scenestack.js"); InsertUnique(includesFiles, "force.js"); InsertUnique(includesFiles, "layer.js"); + InsertUnique(includesFiles, "RuntimeSceneLayer.js"); InsertUnique(includesFiles, "timer.js"); InsertUnique(includesFiles, "runtimegame.js"); InsertUnique(includesFiles, "variable.js"); @@ -572,6 +576,8 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers, InsertUnique(includesFiles, "runtimebehavior.js"); InsertUnique(includesFiles, "spriteruntimeobject.js"); InsertUnique(includesFiles, "affinetransformation.js"); + InsertUnique(includesFiles, "CustomRuntimeObjectInstanceContainer.js"); + InsertUnique(includesFiles, "CustomRuntimeObject.js"); // Common includes for events only. InsertUnique(includesFiles, "events-tools/commontools.js"); @@ -618,6 +624,10 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers, InsertUnique(includesFiles, "pixi-renderers/pixi-bitmapfont-manager.js"); InsertUnique(includesFiles, "pixi-renderers/spriteruntimeobject-pixi-renderer.js"); + InsertUnique(includesFiles, + "pixi-renderers/CustomObjectPixiRenderer.js"); + InsertUnique(includesFiles, + "pixi-renderers/DebuggerPixiRenderer.js"); InsertUnique(includesFiles, "pixi-renderers/loadingscreen-pixi-renderer.js"); InsertUnique(includesFiles, "pixi-renderers/pixi-effects-manager.js"); @@ -819,11 +829,25 @@ void ExporterHelper::ExportObjectAndBehaviorsIncludes( } }; + // TODO UsedExtensionsFinder should be used instead to find the file to include. + // The Exporter class already use it. addObjectsIncludeFiles(project); for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) { const gd::Layout &layout = project.GetLayout(i); addObjectsIncludeFiles(layout); } + + // Event based objects children + for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount(); e++) { + auto& eventsFunctionsExtension = project.GetEventsFunctionsExtension(e); + for (auto&& eventsBasedObjectUniquePtr : + eventsFunctionsExtension.GetEventsBasedObjects() + .GetInternalVector()) { + auto eventsBasedObject = eventsBasedObjectUniquePtr.get(); + + addObjectsIncludeFiles(*eventsBasedObject); + } + } } void ExporterHelper::ExportObjectAndBehaviorsRequiredFiles( diff --git a/GDJS/Runtime/CustomRuntimeObject.ts b/GDJS/Runtime/CustomRuntimeObject.ts new file mode 100644 index 0000000000..2e1245cb4f --- /dev/null +++ b/GDJS/Runtime/CustomRuntimeObject.ts @@ -0,0 +1,591 @@ +/* + * GDevelop JS Platform + * Copyright 2013-2022 Florian Rival (Florian.Rival@gmail.com). All rights reserved. + * This project is released under the MIT License. + */ +namespace gdjs { + export type ObjectConfiguration = { + content: any; + }; + + export type CustomObjectConfiguration = ObjectConfiguration & { + childrenContent: { [objectName: string]: ObjectConfiguration & any }; + }; + + /** + * An object that contains other object. + * + * This is the base class for objects generated from EventsBasedObject. + * + * @see gdjs.CustomRuntimeObjectInstanceContainer + */ + export class CustomRuntimeObject extends gdjs.RuntimeObject { + /** It contains the children of this object. */ + _instanceContainer: gdjs.CustomRuntimeObjectInstanceContainer; + _isUntransformedHitBoxesDirty: boolean = true; + /** It contains shallow copies of the children hitboxes */ + _untransformedHitBoxes: gdjs.Polygon[] = []; + /** The dimension of this object is calculated from its children AABBs. */ + _unrotatedAABB: AABB = { min: [0, 0], max: [0, 0] }; + _scaleX: number = 1; + _scaleY: number = 1; + _flippedX: boolean = false; + _flippedY: boolean = false; + opacity: float = 255; + _objectData: ObjectData & CustomObjectConfiguration; + + /** + * @param parent The container the object belongs to + * @param objectData The object data used to initialize the object + */ + constructor( + parent: gdjs.RuntimeInstanceContainer, + objectData: ObjectData & CustomObjectConfiguration + ) { + super(parent, objectData); + this._instanceContainer = new gdjs.CustomRuntimeObjectInstanceContainer( + parent, + this + ); + this._objectData = objectData; + + this._instanceContainer.loadFrom(objectData); + this.getRenderer().reinitialize(this, parent); + + // The generated code calls the onCreated super implementation at the end. + this.onCreated(); + } + + reinitialize(objectData: ObjectData & CustomObjectConfiguration) { + super.reinitialize(objectData); + + this._instanceContainer.loadFrom(objectData); + this.getRenderer().reinitialize(this, this.getParent()); + + // The generated code calls the onCreated super implementation at the end. + this.onCreated(); + } + + updateFromObjectData( + oldObjectData: ObjectData & CustomObjectConfiguration, + newObjectData: ObjectData & CustomObjectConfiguration + ): boolean { + return this._instanceContainer.updateFrom(oldObjectData, newObjectData); + } + + extraInitializationFromInitialInstance(initialInstanceData: InstanceData) { + if (initialInstanceData.customSize) { + this.setWidth(initialInstanceData.width); + this.setHeight(initialInstanceData.height); + } + } + + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { + // Let subclasses do something before the object is destroyed. + this.onDestroy(instanceContainer); + // Let behaviors do something before the object is destroyed. + super.onDestroyFromScene(instanceContainer); + // Destroy the children. + this._instanceContainer.onDestroyFromScene(instanceContainer); + } + + update(instanceContainer: gdjs.RuntimeInstanceContainer): void { + this._instanceContainer._updateObjectsPreEvents(); + + this.doStepPreEvents(instanceContainer); + + const profiler = this.getRuntimeScene().getProfiler(); + if (profiler) { + profiler.begin(this._objectData.type); + } + // This is a bit like the "scene" events for custom objects. + this.doStepPostEvents(instanceContainer); + if (profiler) { + profiler.end(this._objectData.type); + } + + this._instanceContainer._updateObjectsPostEvents(); + } + + /** + * This method is called when the preview is being hot-reloaded. + */ + onHotReloading(instanceContainer: gdjs.RuntimeInstanceContainer) {} + + // This is only to handle trigger once. + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {} + + /** + * This method is called each tick after events are done. + * @param instanceContainer The instanceContainer owning the object + */ + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {} + + /** + * This method is called when the object is being removed from its parent + * container and is about to be destroyed/reused later. + */ + onDestroy(instanceContainer: gdjs.RuntimeInstanceContainer) {} + + updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer): void { + this._instanceContainer._updateObjectsPreRender(); + this.getRenderer().ensureUpToDate(); + } + + getRendererObject() { + return this.getRenderer().getRendererObject(); + } + + getRenderer() { + return this._instanceContainer.getRenderer(); + } + + onChildrenLocationChanged() { + this._isUntransformedHitBoxesDirty = true; + this.invalidateHitboxes(); + this.getRenderer().update(); + } + + updateHitBoxes(): void { + if (this._isUntransformedHitBoxesDirty) { + this._updateUntransformedHitBoxes(); + } + + //Update the current hitboxes with the frame custom hit boxes + //and apply transformations. + for (let i = 0; i < this._untransformedHitBoxes.length; ++i) { + if (i >= this.hitBoxes.length) { + this.hitBoxes.push(new gdjs.Polygon()); + } + for ( + let j = 0; + j < this._untransformedHitBoxes[i].vertices.length; + ++j + ) { + if (j >= this.hitBoxes[i].vertices.length) { + this.hitBoxes[i].vertices.push([0, 0]); + } + this.applyObjectTransformation( + this._untransformedHitBoxes[i].vertices[j][0], + this._untransformedHitBoxes[i].vertices[j][1], + this.hitBoxes[i].vertices[j] + ); + } + this.hitBoxes[i].vertices.length = this._untransformedHitBoxes[ + i + ].vertices.length; + } + } + + /** + * Merge the hitboxes of the children. + */ + _updateUntransformedHitBoxes() { + this._isUntransformedHitBoxesDirty = false; + + const oldUnscaledCenterX = + (this._unrotatedAABB.max[0] + this._unrotatedAABB.min[0]) / 2; + const oldUnscaledCenterY = + (this._unrotatedAABB.max[1] + this._unrotatedAABB.min[1]) / 2; + + this._untransformedHitBoxes.length = 0; + if (this._instanceContainer.getAdhocListOfAllInstances().length === 0) { + this._unrotatedAABB.min[0] = 0; + this._unrotatedAABB.min[1] = 0; + this._unrotatedAABB.max[0] = 0; + this._unrotatedAABB.max[1] = 0; + } else { + let minX = Number.MAX_VALUE; + let minY = Number.MAX_VALUE; + let maxX = -Number.MAX_VALUE; + let maxY = -Number.MAX_VALUE; + for (const childInstance of this._instanceContainer.getAdhocListOfAllInstances()) { + Array.prototype.push.apply( + this._untransformedHitBoxes, + childInstance.getHitBoxes() + ); + const childAABB = childInstance.getAABB(); + minX = Math.min(minX, childAABB.min[0]); + minY = Math.min(minY, childAABB.min[1]); + maxX = Math.max(maxX, childAABB.max[0]); + maxY = Math.max(maxY, childAABB.max[1]); + } + this._unrotatedAABB.min[0] = minX; + this._unrotatedAABB.min[1] = minY; + this._unrotatedAABB.max[0] = maxX; + this._unrotatedAABB.max[1] = maxY; + + while (this.hitBoxes.length < this._untransformedHitBoxes.length) { + this.hitBoxes.push(new gdjs.Polygon()); + } + this.hitBoxes.length = this._untransformedHitBoxes.length; + } + + if ( + this.getUnscaledCenterX() !== oldUnscaledCenterX || + this.getUnscaledCenterY() !== oldUnscaledCenterY + ) { + this._instanceContainer.onObjectUnscaledCenterChanged( + oldUnscaledCenterX, + oldUnscaledCenterY + ); + } + } + + // Position: + /** + * Return an array containing the coordinates of the point passed as parameter + * in parent coordinate coordinates (as opposed to the object local coordinates). + * + * All transformations (flipping, scale, rotation) are supported. + * + * @param x The X position of the point, in object coordinates. + * @param y The Y position of the point, in object coordinates. + * @param result Array that will be updated with the result + * (x and y position of the point in parent coordinates). + */ + applyObjectTransformation(x: float, y: float, result: number[]) { + let cx = this.getCenterX(); + let cy = this.getCenterY(); + + // Flipping + if (this._flippedX) { + x = x + (cx - x) * 2; + } + if (this._flippedY) { + y = y + (cy - y) * 2; + } + + // Scale + const absScaleX = Math.abs(this._scaleX); + const absScaleY = Math.abs(this._scaleY); + x *= absScaleX; + y *= absScaleY; + cx *= absScaleX; + cy *= absScaleY; + + // Rotation + const oldX = x; + const angleInRadians = (this.angle / 180) * Math.PI; + const cosValue = Math.cos(angleInRadians); + const sinValue = Math.sin(angleInRadians); + const xToCenterXDelta = x - cx; + const yToCenterYDelta = y - cy; + x = cx + cosValue * xToCenterXDelta - sinValue * yToCenterYDelta; + y = cy + sinValue * xToCenterXDelta + cosValue * yToCenterYDelta; + result.length = 2; + result[0] = x + this.x; + result[1] = y + this.y; + } + + /** + * Return an array containing the coordinates of the point passed as parameter + * in object local coordinates (as opposed to the parent coordinate coordinates). + * + * All transformations (flipping, scale, rotation) are supported. + * + * @param x The X position of the point, in parent coordinates. + * @param y The Y position of the point, in parent coordinates. + * @param result Array that will be updated with the result + * (x and y position of the point in object coordinates). + */ + applyObjectInverseTransformation(x: float, y: float, result: number[]) { + x -= this.getCenterXInScene(); + y -= this.getCenterYInScene(); + + const absScaleX = Math.abs(this._scaleX); + const absScaleY = Math.abs(this._scaleY); + + // Rotation + const angleInRadians = (this.angle / 180) * Math.PI; + const cosValue = Math.cos(-angleInRadians); + const sinValue = Math.sin(-angleInRadians); + const oldX = x; + x = cosValue * x - sinValue * y; + y = sinValue * oldX + cosValue * y; + + // Scale + x /= absScaleX; + y /= absScaleY; + + // Flipping + if (this._flippedX) { + x = -x; + } + if (this._flippedY) { + y = -y; + } + + const positionToCenterX = + this.getUnscaledWidth() / 2 + this._unrotatedAABB.min[0]; + const positionToCenterY = + this.getUnscaledHeight() / 2 + this._unrotatedAABB.min[1]; + result[0] = x + positionToCenterX; + result[1] = y + positionToCenterY; + } + + getDrawableX(): float { + if (this._isUntransformedHitBoxesDirty) { + this._updateUntransformedHitBoxes(); + } + return this.x + this._unrotatedAABB.min[0] * this._scaleX; + } + + getDrawableY(): float { + if (this._isUntransformedHitBoxesDirty) { + this._updateUntransformedHitBoxes(); + } + return this.y + this._unrotatedAABB.min[1] * this._scaleY; + } + + /** + * @return the internal width of the object according to its children. + */ + getUnscaledWidth(): float { + if (this._isUntransformedHitBoxesDirty) { + this._updateUntransformedHitBoxes(); + } + return this._unrotatedAABB.max[0] - this._unrotatedAABB.min[0]; + } + + /** + * @return the internal height of the object according to its children. + */ + getUnscaledHeight(): float { + if (this._isUntransformedHitBoxesDirty) { + this._updateUntransformedHitBoxes(); + } + return this._unrotatedAABB.max[1] - this._unrotatedAABB.min[1]; + } + + /** + * @returns the center X from the local origin (0;0). + */ + getUnscaledCenterX(): float { + if (this._isUntransformedHitBoxesDirty) { + this._updateUntransformedHitBoxes(); + } + return (this._unrotatedAABB.min[0] + this._unrotatedAABB.max[0]) / 2; + } + + /** + * @returns the center Y from the local origin (0;0). + */ + getUnscaledCenterY(): float { + if (this._isUntransformedHitBoxesDirty) { + this._updateUntransformedHitBoxes(); + } + return (this._unrotatedAABB.min[1] + this._unrotatedAABB.max[1]) / 2; + } + + getWidth(): float { + return this.getUnscaledWidth() * this.getScaleX(); + } + + getHeight(): float { + return this.getUnscaledHeight() * this.getScaleY(); + } + + setWidth(newWidth: float): void { + const unscaledWidth = this.getUnscaledWidth(); + if (unscaledWidth !== 0) { + this.setScaleX(newWidth / unscaledWidth); + } + } + + setHeight(newHeight: float): void { + const unscaledHeight = this.getUnscaledHeight(); + if (unscaledHeight !== 0) { + this.setScaleY(newHeight / unscaledHeight); + } + } + + /** + * Change the size of the object. + * + * @param newWidth The new width of the object, in pixels. + * @param newHeight The new height of the object, in pixels. + */ + setSize(newWidth: float, newHeight: float): void { + this.setWidth(newWidth); + this.setHeight(newHeight); + } + + setX(x: float): void { + if (x === this.x) { + return; + } + this.x = x; + this.invalidateHitboxes(); + this.getRenderer().updateX(); + } + + setY(y: float): void { + if (y === this.y) { + return; + } + this.y = y; + this.invalidateHitboxes(); + this.getRenderer().updateY(); + } + + setAngle(angle: float): void { + if (this.angle === angle) { + return; + } + this.angle = angle; + this.invalidateHitboxes(); + this.getRenderer().updateAngle(); + } + + /** + * Change the scale on X and Y axis of the object. + * + * @param newScale The new scale (must be greater than 0). + */ + setScale(newScale: number): void { + if (newScale < 0) { + newScale = 0; + } + if ( + newScale === Math.abs(this._scaleX) && + newScale === Math.abs(this._scaleY) + ) { + return; + } + this._scaleX = newScale * (this._flippedX ? -1 : 1); + this._scaleY = newScale * (this._flippedY ? -1 : 1); + this.invalidateHitboxes(); + this.getRenderer().update(); + } + + /** + * Change the scale on X axis of the object (changing its width). + * + * @param newScale The new scale (must be greater than 0). + */ + setScaleX(newScale: number): void { + if (newScale < 0) { + newScale = 0; + } + if (newScale === Math.abs(this._scaleX)) { + return; + } + this._scaleX = newScale * (this._flippedX ? -1 : 1); + this.invalidateHitboxes(); + this.getRenderer().update(); + } + + /** + * Change the scale on Y axis of the object (changing its height). + * + * @param newScale The new scale (must be greater than 0). + */ + setScaleY(newScale: number): void { + if (newScale < 0) { + newScale = 0; + } + if (newScale === Math.abs(this._scaleY)) { + return; + } + this._scaleY = newScale * (this._flippedY ? -1 : 1); + this.invalidateHitboxes(); + this.getRenderer().update(); + } + + /** + * Get the scale of the object (or the average of the X and Y scale in case + * they are different). + * + * @return the scale of the object (or the average of the X and Y scale in + * case they are different). + */ + getScale(): number { + return (Math.abs(this._scaleX) + Math.abs(this._scaleY)) / 2.0; + } + + /** + * Get the scale of the object on Y axis. + * + * @return the scale of the object on Y axis + */ + getScaleY(): float { + return Math.abs(this._scaleY); + } + + /** + * Get the scale of the object on X axis. + * + * @return the scale of the object on X axis + */ + getScaleX(): float { + return Math.abs(this._scaleX); + } + + // Visibility and display : + /** + * Change the transparency of the object. + * @param opacity The new opacity, between 0 (transparent) and 255 (opaque). + */ + setOpacity(opacity: float): void { + if (opacity < 0) { + opacity = 0; + } + if (opacity > 255) { + opacity = 255; + } + this.opacity = opacity; + this.getRenderer().updateOpacity(); + } + + /** + * Get the transparency of the object. + * @return The opacity, between 0 (transparent) and 255 (opaque). + */ + getOpacity(): number { + return this.opacity; + } + + /** + * Hide (or show) the object + * @param enable true to hide the object, false to show it again. + */ + hide(enable: boolean): void { + if (enable === undefined) { + enable = true; + } + this.hidden = enable; + this.getRenderer().updateVisibility(); + } + + flipX(enable: boolean) { + if (enable !== this._flippedX) { + this._scaleX *= -1; + this._flippedX = enable; + this.invalidateHitboxes(); + this.getRenderer().update(); + } + } + + flipY(enable: boolean) { + if (enable !== this._flippedY) { + this._scaleY *= -1; + this._flippedY = enable; + this.invalidateHitboxes(); + this.getRenderer().update(); + } + } + + isFlippedX(): boolean { + return this._flippedX; + } + + isFlippedY(): boolean { + return this._flippedY; + } + } + + // Others initialization and internal state management : + // TODO EBO Activate and test instance recycling. + CustomRuntimeObject.supportsReinitialization = false; +} diff --git a/GDJS/Runtime/CustomRuntimeObjectInstanceContainer.ts b/GDJS/Runtime/CustomRuntimeObjectInstanceContainer.ts new file mode 100644 index 0000000000..38972478bc --- /dev/null +++ b/GDJS/Runtime/CustomRuntimeObjectInstanceContainer.ts @@ -0,0 +1,354 @@ +/* + * GDevelop JS Platform + * Copyright 2013-2022 Florian Rival (Florian.Rival@gmail.com). All rights reserved. + * This project is released under the MIT License. + */ +namespace gdjs { + const logger = new gdjs.Logger('CustomRuntimeObject'); + const setupWarningLogger = new gdjs.Logger( + 'CustomRuntimeObject (setup warnings)' + ); + + /** + * The instance container of a custom object, containing instances of objects rendered on screen. + * + * @see gdjs.CustomRuntimeObject + */ + export class CustomRuntimeObjectInstanceContainer extends gdjs.RuntimeInstanceContainer { + _renderer: gdjs.CustomObjectRenderer; + _debuggerRenderer: gdjs.DebuggerRenderer; + _runtimeScene: gdjs.RuntimeScene; + /** The parent container that contains the object associated with this container. */ + _parent: gdjs.RuntimeInstanceContainer; + /** The object that is built with the instances of this container. */ + _customObject: gdjs.CustomRuntimeObject; + _isLoaded: boolean = false; + + /** + * @param parent the parent container that contains the object associated + * with this container. + * @param customObject the object that is built with the instances of this + * container. + */ + constructor( + parent: gdjs.RuntimeInstanceContainer, + customObject: gdjs.CustomRuntimeObject + ) { + super(); + this._parent = parent; + this._customObject = customObject; + this._runtimeScene = parent.getScene(); + this._renderer = new gdjs.CustomObjectRenderer( + customObject, + this, + parent + ); + this._debuggerRenderer = new gdjs.DebuggerRenderer(this); + } + + createObject(objectName: string): gdjs.RuntimeObject | null { + const result = super.createObject(objectName); + this._customObject.onChildrenLocationChanged(); + return result; + } + + /** + * Load the container from the given initial configuration. + * @param customObjectData An object containing the container data. + * @see gdjs.RuntimeGame#getSceneData + */ + loadFrom(customObjectData: ObjectData & CustomObjectConfiguration) { + if (this._isLoaded) { + this.onDestroyFromScene(this._parent); + } + + const eventsBasedObjectData = this._runtimeScene + .getGame() + .getEventsBasedObjectData(customObjectData.type); + if (!eventsBasedObjectData) { + logger.error('loadFrom was called without an events-based object'); + return; + } + + // Registering objects + for ( + let i = 0, len = eventsBasedObjectData.objects.length; + i < len; + ++i + ) { + const childObjectData = eventsBasedObjectData.objects[i]; + this.registerObject({ + ...childObjectData, + ...customObjectData.childrenContent[childObjectData.name], + }); + } + + // TODO EBO Remove it when the instance editor is done. + // Add a default layer + this.addLayer({ + name: '', + visibility: true, + cameras: [ + { + defaultSize: true, + defaultViewport: true, + height: 0, + viewportBottom: 0, + viewportLeft: 0, + viewportRight: 0, + viewportTop: 0, + width: 0, + }, + ], + effects: [], + ambientLightColorR: 0, + ambientLightColorG: 0, + ambientLightColorB: 0, + isLightingLayer: false, + followBaseLayerCamera: false, + }); + + // Set up the default z order (for objects created from events) + this._setLayerDefaultZOrders(); + + this._isLoaded = true; + } + + /** + * Called when the container must be updated using the specified + * objectData. This is the case during hot-reload, and is only called if + * the object was modified. + * + * @param oldCustomObjectData The previous data for the object. + * @param newCustomObjectData The new data for the object. + * @returns true if the object was updated, false if it could not + * (i.e: hot-reload is not supported). + */ + updateFrom( + oldCustomObjectData: ObjectData & CustomObjectConfiguration, + newCustomObjectData: ObjectData & CustomObjectConfiguration + ): boolean { + const eventsBasedObjectData = this._runtimeScene + .getGame() + .getEventsBasedObjectData(newCustomObjectData.type); + if (!eventsBasedObjectData) { + logger.error('updateFrom was called without an events-based object'); + return false; + } + + for ( + let i = 0, len = eventsBasedObjectData.objects.length; + i < len; + ++i + ) { + const childName = eventsBasedObjectData.objects[i].name; + const oldChildData = { + ...eventsBasedObjectData.objects[i], + ...oldCustomObjectData.childrenContent[childName], + }; + const newChildData = { + ...eventsBasedObjectData.objects[i], + ...newCustomObjectData.childrenContent[childName], + }; + this.updateObject(newChildData); + + for (const child of this.getInstancesOf(childName)) { + child.updateFromObjectData(oldChildData, newChildData); + } + } + return true; + } + + /** + * Called when the associated object is destroyed (because it is removed + * from its parent container or the scene is being unloaded). + * + * @param instanceContainer The container owning the object. + */ + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { + if (!this._isLoaded) { + return; + } + + // Notify the objects they are being destroyed + const allInstancesList = this.getAdhocListOfAllInstances(); + for (let i = 0, len = allInstancesList.length; i < len; ++i) { + const object = allInstancesList[i]; + object.onDestroyFromScene(this); + } + + this._destroy(); + + this._isLoaded = false; + } + + _destroy() { + // It should not be necessary to reset these variables, but this help + // ensuring that all memory related to the container is released immediately. + super._destroy(); + // @ts-ignore We are deleting the object + this._onceTriggers = null; + } + + /** + * Called to update visibility of the renderers of objects + * rendered on the scene ("culling"), update effects (of visible objects) + * and give a last chance for objects to update before rendering. + * + * Visibility is set to false if object is hidden, or if + * object is too far from the camera of its layer ("culling"). + */ + _updateObjectsPreRender() { + const allInstancesList = this.getAdhocListOfAllInstances(); + for ( + let i = 0, len = this.getAdhocListOfAllInstances().length; + i < len; + ++i + ) { + const object = this.getAdhocListOfAllInstances()[i]; + const rendererObject = object.getRendererObject(); + if (rendererObject) { + rendererObject.visible = !object.isHidden(); + + // Update effects, only for visible objects. + if (rendererObject.visible) { + this.getGame() + .getEffectsManager() + .updatePreRender(object.getRendererEffects(), object); + } + } + + // Set to true to enable debug rendering (look for the implementation in the renderer + // to see what is rendered). + if (this._debugDrawEnabled) { + this._debuggerRenderer.renderDebugDraw( + this.getAdhocListOfAllInstances(), + this._debugDrawShowHiddenInstances, + this._debugDrawShowPointsNames, + this._debugDrawShowCustomPoints + ); + } + + // Perform pre-render update. + object.updatePreRender(this); + } + return; + } + + /** + * Update the objects before launching the events. + */ + _updateObjectsPreEvents() { + const allInstancesList = this.getAdhocListOfAllInstances(); + for (let i = 0, len = allInstancesList.length; i < len; ++i) { + const obj = allInstancesList[i]; + const elapsedTime = obj.getElapsedTime(); + if (!obj.hasNoForces()) { + const averageForce = obj.getAverageForce(); + const elapsedTimeInSeconds = elapsedTime / 1000; + obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds); + obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds); + obj.update(this); + obj.updateForces(elapsedTimeInSeconds); + } else { + obj.update(this); + } + obj.updateTimers(elapsedTime); + obj.stepBehaviorsPreEvents(this); + } + + // Some behaviors may have request objects to be deleted. + this._cacheOrClearRemovedInstances(); + } + + /** + * Get the renderer associated to the RuntimeScene. + */ + getRenderer(): gdjs.CustomObjectRenderer { + return this._renderer; + } + + getDebuggerRenderer() { + return this._debuggerRenderer; + } + + getGame() { + return this._runtimeScene.getGame(); + } + + getScene() { + return this._runtimeScene; + } + + getViewportWidth(): float { + return this._customObject.getUnscaledWidth(); + } + + getViewportHeight(): float { + return this._customObject.getUnscaledHeight(); + } + + getViewportOriginX(): float { + return this._customObject.getUnscaledCenterX(); + } + + getViewportOriginY(): float { + return this._customObject.getUnscaledCenterY(); + } + + onChildrenLocationChanged(): void { + this._customObject.onChildrenLocationChanged(); + } + + /** + * Triggered when the object dimensions are changed. + * + * It adapts the layers camera positions. + */ + onObjectUnscaledCenterChanged(oldOriginX: float, oldOriginY: float): void { + for (const name in this._layers.items) { + if (this._layers.items.hasOwnProperty(name)) { + /** @type gdjs.Layer */ + const theLayer: gdjs.Layer = this._layers.items[name]; + theLayer.onGameResolutionResized(oldOriginX, oldOriginY); + } + } + } + + convertCoords(x: float, y: float, result: FloatPoint): FloatPoint { + // The result parameter used to be optional. + let position = result || [0, 0]; + position = this._parent + .getLayer(this._customObject.getLayer()) + .convertCoords(x, y, 0, position); + this._customObject.applyObjectInverseTransformation( + position[0], + position[1], + position + ); + return position; + } + + convertInverseCoords( + sceneX: float, + sceneY: float, + result: FloatPoint + ): FloatPoint { + const position = result || [0, 0]; + this._customObject.applyObjectTransformation(sceneX, sceneY, position); + return this._parent.convertInverseCoords( + position[0], + position[1], + position + ); + } + + /** + * Return the time elapsed since the last frame, + * in milliseconds, for objects on the layer. + */ + getElapsedTime(): float { + return this._parent.getElapsedTime(); + } + } +} diff --git a/GDJS/Runtime/RuntimeInstanceContainer.ts b/GDJS/Runtime/RuntimeInstanceContainer.ts new file mode 100644 index 0000000000..91f7d14ec4 --- /dev/null +++ b/GDJS/Runtime/RuntimeInstanceContainer.ts @@ -0,0 +1,694 @@ +/* + * GDevelop JS Platform + * Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved. + * This project is released under the MIT License. + */ +namespace gdjs { + const logger = new gdjs.Logger('RuntimeInstanceContainer'); + const setupWarningLogger = new gdjs.Logger( + 'RuntimeInstanceContainer (setup warnings)' + ); + + /** + * A container of object instances rendered on screen. + */ + export abstract class RuntimeInstanceContainer { + /** Contains the instances living on the container */ + _instances: Hashtable; + + /** + * An array used to create a list of all instance when necessary. + * @see gdjs.RuntimeInstanceContainer#_constructListOfAllInstances} + */ + private _allInstancesList: gdjs.RuntimeObject[] = []; + _allInstancesListIsUpToDate = true; + + /** Used to recycle destroyed instance instead of creating new ones. */ + _instancesCache: Hashtable; + + /** The instances removed from the container and waiting to be sent to the cache. */ + _instancesRemoved: gdjs.RuntimeObject[] = []; + + /** Contains the objects data stored in the project */ + _objects: Hashtable; + _objectsCtor: Hashtable; + + _layers: Hashtable; + _layersCameraCoordinates: Record = {}; + + // Options for the debug draw: + _debugDrawEnabled: boolean = false; + _debugDrawShowHiddenInstances: boolean = false; + _debugDrawShowPointsNames: boolean = false; + _debugDrawShowCustomPoints: boolean = false; + + constructor() { + this._instances = new Hashtable(); + this._instancesCache = new Hashtable(); + this._objects = new Hashtable(); + this._objectsCtor = new Hashtable(); + this._layers = new Hashtable(); + } + + /** + * Return the time elapsed since the last frame, + * in milliseconds, for objects on the layer. + */ + abstract getElapsedTime(): float; + + /** + * Get the renderer associated to the container. + */ + abstract getRenderer(): gdjs.RuntimeInstanceContainerRenderer; + + /** + * Get the renderer for visual debugging associated to the container. + */ + abstract getDebuggerRenderer(): gdjs.DebuggerRenderer; + + /** + * Get the {@link gdjs.RuntimeGame} associated to this. + */ + abstract getGame(): gdjs.RuntimeGame; + + /** + * Get the {@link gdjs.RuntimeScene} associated to this. + */ + abstract getScene(): gdjs.RuntimeScene; + + /** + * Convert a point from the canvas coordinates (for example, + * the mouse position) to the container coordinates. + * + * @param x The x position, in container coordinates. + * @param y The y position, in container coordinates. + * @param result The point instance that is used to return the result. + */ + abstract convertCoords(x: float, y: float, result?: FloatPoint): FloatPoint; + + /** + * Convert a point from the container coordinates (for example, + * an object position) to the canvas coordinates. + * + * @param sceneX The x position, in container coordinates. + * @param sceneY The y position, in container coordinates. + * @param result The point instance that is used to return the result. + */ + abstract convertInverseCoords( + sceneX: float, + sceneY: float, + result: FloatPoint + ): FloatPoint; + + /** + * @return the width of: + * - the game resolution for a {@link gdjs.RuntimeScene} + * - the default dimensions (the AABB of all its children) for a + * {@link gdjs.CustomRuntimeObject}. + */ + abstract getViewportWidth(): float; + + /** + * @return the height of: + * - the game resolution for a {@link gdjs.RuntimeScene} + * - the default dimensions (the AABB of all its children) for a + * {@link gdjs.CustomRuntimeObject}. + */ + abstract getViewportHeight(): float; + + /** + * @return the center X of: + * - the game resolution for a {@link gdjs.RuntimeScene} + * - the default dimensions (the AABB of all its children) for a + * {@link gdjs.CustomRuntimeObject}. + */ + abstract getViewportOriginX(): float; + + /** + * @return the center Y of: + * - the game resolution for a {@link gdjs.RuntimeScene} + * - the default dimensions (the AABB of all its children) for a + * {@link gdjs.CustomRuntimeObject}. + */ + abstract getViewportOriginY(): float; + + /** + * Triggered when the AABB of a child of the container could have changed. + */ + abstract onChildrenLocationChanged(): void; + + /** + * Activate or deactivate the debug visualization for collisions and points. + */ + enableDebugDraw( + enableDebugDraw: boolean, + showHiddenInstances: boolean, + showPointsNames: boolean, + showCustomPoints: boolean + ): void { + if (this._debugDrawEnabled && !enableDebugDraw) { + this.getDebuggerRenderer().clearDebugDraw(); + } + + this._debugDrawEnabled = enableDebugDraw; + this._debugDrawShowHiddenInstances = showHiddenInstances; + this._debugDrawShowPointsNames = showPointsNames; + this._debugDrawShowCustomPoints = showCustomPoints; + } + + /** + * Check if an object is registered, meaning that instances of it can be + * created and lives in the container. + * @see gdjs.RuntimeInstanceContainer#registerObject + */ + isObjectRegistered(objectName: string): boolean { + return ( + this._objects.containsKey(objectName) && + this._instances.containsKey(objectName) && + this._objectsCtor.containsKey(objectName) + ); + } + + /** + * Register a {@link gdjs.RuntimeObject} so that instances of it can be + * used in the container. + * @param objectData The data for the object to register. + */ + registerObject(objectData: ObjectData) { + this._objects.put(objectData.name, objectData); + this._instances.put(objectData.name, []); + + // Cache the constructor + const Ctor = gdjs.getObjectConstructor(objectData.type); + this._objectsCtor.put(objectData.name, Ctor); + + // Also prepare a cache for recycled instances, if the object supports it. + if (Ctor.supportsReinitialization) { + this._instancesCache.put(objectData.name, []); + } + } + + /** + * Update the data of a {@link gdjs.RuntimeObject} so that instances use + * this when constructed. + * @param objectData The data for the object to register. + */ + updateObject(objectData: ObjectData): void { + if (!this.isObjectRegistered(objectData.name)) { + logger.warn( + 'Tried to call updateObject for an object that was not registered (' + + objectData.name + + '). Call registerObject first.' + ); + } + this._objects.put(objectData.name, objectData); + } + + // Don't erase instances, nor instances cache, or objectsCtor cache. + /** + * Unregister a {@link gdjs.RuntimeObject}. Instances will be destroyed. + * @param objectName The name of the object to unregister. + */ + unregisterObject(objectName: string) { + const instances = this._instances.get(objectName); + if (instances) { + // This is sub-optimal: markObjectForDeletion will search the instance to + // remove in instances, so cost is O(n^2), n being the number of instances. + // As we're unregistering an object which only happen during a hot-reloading, + // this is fine. + const instancesToRemove = instances.slice(); + for (let i = 0; i < instancesToRemove.length; i++) { + this.markObjectForDeletion(instancesToRemove[i]); + } + this._cacheOrClearRemovedInstances(); + } + this._objects.remove(objectName); + this._instances.remove(objectName); + this._instancesCache.remove(objectName); + this._objectsCtor.remove(objectName); + } + + /** + * Create objects from initial instances data (for example, the initial instances + * of the scene or the instances of an external layout). + * + * @param data The instances data + * @param xPos The offset on X axis + * @param yPos The offset on Y axis + * @param trackByPersistentUuid If true, objects are tracked by setting their `persistentUuid` + * to the same as the associated instance. Useful for hot-reloading when instances are changed. + */ + createObjectsFrom( + data: InstanceData[], + xPos: float, + yPos: float, + trackByPersistentUuid: boolean + ) { + for (let i = 0, len = data.length; i < len; ++i) { + const instanceData = data[i]; + const objectName = instanceData.name; + const newObject = this.createObject(objectName); + if (newObject !== null) { + if (trackByPersistentUuid) { + // Give the object the same persistentUuid as the instance, so that + // it can be hot-reloaded. + newObject.persistentUuid = instanceData.persistentUuid || null; + } + newObject.setPosition(instanceData.x + xPos, instanceData.y + yPos); + newObject.setZOrder(instanceData.zOrder); + newObject.setAngle(instanceData.angle); + newObject.setLayer(instanceData.layer); + newObject + .getVariables() + .initFrom(instanceData.initialVariables, true); + newObject.extraInitializationFromInitialInstance(instanceData); + } + } + } + + /** + * Set the default Z order for each layer, which is the highest Z order found on each layer. + * Useful as it ensures that instances created from events are, by default, shown in front + * of other instances. + */ + _setLayerDefaultZOrders() { + if ( + this.getGame().getGameData().properties.useDeprecatedZeroAsDefaultZOrder + ) { + // Deprecated option to still support games that were made considered 0 as the + // default Z order for all layers. + return; + } + const layerHighestZOrders: Record = {}; + const allInstances = this.getAdhocListOfAllInstances(); + for (let i = 0, len = allInstances.length; i < len; ++i) { + const object = allInstances[i]; + let layerName = object.getLayer(); + const zOrder = object.getZOrder(); + if ( + layerHighestZOrders[layerName] === undefined || + layerHighestZOrders[layerName] < zOrder + ) { + layerHighestZOrders[layerName] = zOrder; + } + } + for (let layerName in layerHighestZOrders) { + this.getLayer(layerName).setDefaultZOrder( + layerHighestZOrders[layerName] + 1 + ); + } + } + + _updateLayersCameraCoordinates(scale: float) { + this._layersCameraCoordinates = this._layersCameraCoordinates || {}; + for (const name in this._layers.items) { + if (this._layers.items.hasOwnProperty(name)) { + const theLayer = this._layers.items[name]; + this._layersCameraCoordinates[name] = this._layersCameraCoordinates[ + name + ] || [0, 0, 0, 0]; + this._layersCameraCoordinates[name][0] = + theLayer.getCameraX() - (theLayer.getCameraWidth() / 2) * scale; + this._layersCameraCoordinates[name][1] = + theLayer.getCameraY() - (theLayer.getCameraHeight() / 2) * scale; + this._layersCameraCoordinates[name][2] = + theLayer.getCameraX() + (theLayer.getCameraWidth() / 2) * scale; + this._layersCameraCoordinates[name][3] = + theLayer.getCameraY() + (theLayer.getCameraHeight() / 2) * scale; + } + } + } + + /** + * Called to update effects of layers before rendering. + */ + _updateLayersPreRender() { + for (const name in this._layers.items) { + if (this._layers.items.hasOwnProperty(name)) { + const layer = this._layers.items[name]; + layer.updatePreRender(this); + } + } + } + + /** + * Called to update visibility of the renderers of objects + * rendered on the scene ("culling"), update effects (of visible objects) + * and give a last chance for objects to update before rendering. + * + * Visibility is set to false if object is hidden, or if + * object is too far from the camera of its layer ("culling"). + */ + _updateObjectsPreRender() { + const allInstancesList = this.getAdhocListOfAllInstances(); + for (let i = 0, len = allInstancesList.length; i < len; ++i) { + const object = allInstancesList[i]; + const rendererObject = object.getRendererObject(); + if (rendererObject) { + rendererObject.visible = !object.isHidden(); + + // Update effects, only for visible objects. + if (rendererObject.visible) { + this.getGame() + .getEffectsManager() + .updatePreRender(object.getRendererEffects(), object); + } + } + + // Perform pre-render update. + object.updatePreRender(this); + } + return; + } + + /** + * Empty the list of the removed objects: + * + * When an object is removed from the container, it is still kept in + * {@link gdjs.RuntimeInstanceContainer#_instancesRemoved}. + * + * This method should be called regularly (after events or behaviors steps) so as to clear this list + * and allows the removed objects to be cached (or destroyed if the cache is full). + * + * The removed objects could not be sent directly to the cache, as events may still be using them after + * removing them from the scene for example. + */ + _cacheOrClearRemovedInstances() { + for (let k = 0, lenk = this._instancesRemoved.length; k < lenk; ++k) { + // Cache the instance to recycle it into a new instance later. + // If the object does not support recycling, the cache won't be defined. + const cache = this._instancesCache.get( + this._instancesRemoved[k].getName() + ); + if (cache) { + if (cache.length < 128) { + cache.push(this._instancesRemoved[k]); + } + } + } + this._instancesRemoved.length = 0; + } + + /** + * Tool function filling _allInstancesList member with all the living object instances. + */ + private _constructListOfAllInstances() { + let currentListSize = 0; + for (const name in this._instances.items) { + if (this._instances.items.hasOwnProperty(name)) { + const list = this._instances.items[name]; + const oldSize = currentListSize; + currentListSize += list.length; + for (let j = 0, lenj = list.length; j < lenj; ++j) { + if (oldSize + j < this._allInstancesList.length) { + this._allInstancesList[oldSize + j] = list[j]; + } else { + this._allInstancesList.push(list[j]); + } + } + } + } + this._allInstancesList.length = currentListSize; + this._allInstancesListIsUpToDate = true; + } + + /** + * @param objectName The name of the object + * @returns the instances of a given object in the container. + */ + getInstancesOf(objectName: string): gdjs.RuntimeObject[] { + return this._instances.items[objectName]; + } + + /** + * Get a list of all {@link gdjs.RuntimeObject} living in the container. + * You should not, normally, need this method at all. It's only to be used + * in exceptional use cases where you need to loop through all objects, + * and it won't be performant. + * + * @returns The list of all runtime objects in the container + */ + getAdhocListOfAllInstances(): gdjs.RuntimeObject[] { + if (!this._allInstancesListIsUpToDate) { + this._constructListOfAllInstances(); + } + return this._allInstancesList; + } + + /** + * Update the objects before launching the events. + */ + _updateObjectsPreEvents() { + // It is *mandatory* to create and iterate on a external list of all objects, as the behaviors + // may delete the objects. + const allInstancesList = this.getAdhocListOfAllInstances(); + for (let i = 0, len = allInstancesList.length; i < len; ++i) { + const obj = allInstancesList[i]; + const elapsedTime = obj.getElapsedTime(); + if (!obj.hasNoForces()) { + const averageForce = obj.getAverageForce(); + const elapsedTimeInSeconds = elapsedTime / 1000; + obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds); + obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds); + obj.update(this); + obj.updateForces(elapsedTimeInSeconds); + } else { + obj.update(this); + } + obj.updateTimers(elapsedTime); + allInstancesList[i].stepBehaviorsPreEvents(this); + } + + // Some behaviors may have request objects to be deleted. + this._cacheOrClearRemovedInstances(); + } + + /** + * Update the objects (update positions, time management...) + */ + _updateObjectsPostEvents() { + this._cacheOrClearRemovedInstances(); + + // It is *mandatory* to create and iterate on a external list of all objects, as the behaviors + // may delete the objects. + const allInstancesList = this.getAdhocListOfAllInstances(); + for (let i = 0, len = allInstancesList.length; i < len; ++i) { + allInstancesList[i].stepBehaviorsPostEvents(this); + } + + // Some behaviors may have request objects to be deleted. + this._cacheOrClearRemovedInstances(); + } + + /** + * Add an object to the instances living in the container. + * @param obj The object to be added. + */ + addObject(obj: gdjs.RuntimeObject) { + if (!this._instances.containsKey(obj.name)) { + this._instances.put(obj.name, []); + } + this._instances.get(obj.name).push(obj); + this._allInstancesListIsUpToDate = false; + } + + /** + * Get all the instances of the object called name. + * @param name Name of the object for which the instances must be returned. + * @return The list of objects with the given name + */ + getObjects(name: string): gdjs.RuntimeObject[] | undefined { + if (!this._instances.containsKey(name)) { + logger.info( + 'RuntimeScene.getObjects: No instances called "' + + name + + '"! Adding it.' + ); + this._instances.put(name, []); + } + return this._instances.get(name); + } + + /** + * Create a new object from its name. The object is also added to the instances + * living in the container (No need to call {@link gdjs.RuntimeScene.addObject}) + * @param objectName The name of the object to be created + * @return The created object + */ + createObject(objectName: string): gdjs.RuntimeObject | null { + if ( + !this._objectsCtor.containsKey(objectName) || + !this._objects.containsKey(objectName) + ) { + // There is no such object in this container. + return null; + } + + // Create a new object using the object constructor (cached during loading) + // and the stored object's data: + const cache = this._instancesCache.get(objectName); + const ctor = this._objectsCtor.get(objectName); + let obj; + if (!cache || cache.length === 0) { + obj = new ctor(this, this._objects.get(objectName)); + } else { + // Reuse an objet destroyed before. If there is an object in the cache, + // then it means it does support reinitialization. + obj = cache.pop(); + obj.reinitialize(this._objects.get(objectName)); + } + this.addObject(obj); + return obj; + } + + /** + * Must be called whenever an object must be removed from the container. + * @param obj The object to be removed. + */ + markObjectForDeletion(obj: gdjs.RuntimeObject) { + // Add to the objects removed list. + // The objects will be sent to the instances cache or really deleted from memory later. + if (this._instancesRemoved.indexOf(obj) === -1) { + this._instancesRemoved.push(obj); + } + + // Delete from the living instances. + if (this._instances.containsKey(obj.getName())) { + const objId = obj.id; + const allInstances = this._instances.get(obj.getName()); + for (let i = 0, len = allInstances.length; i < len; ++i) { + if (allInstances[i].id == objId) { + allInstances.splice(i, 1); + this._allInstancesListIsUpToDate = false; + break; + } + } + } + + // Notify the object it was removed from the container + obj.onDestroyFromScene(this); + + // Notify the global callbacks + for (let j = 0; j < gdjs.callbacksObjectDeletedFromScene.length; ++j) { + gdjs.callbacksObjectDeletedFromScene[j](this, obj); + } + return; + } + + /** + * Get the layer with the given name + * @param name The name of the layer + * @returns The layer, or the base layer if not found + */ + getLayer(name: string): gdjs.Layer { + if (this._layers.containsKey(name)) { + return this._layers.get(name); + } + return this._layers.get(''); + } + + /** + * Check if a layer exists + * @param name The name of the layer + */ + hasLayer(name: string): boolean { + return this._layers.containsKey(name); + } + + /** + * Add a layer. + * @param layerData The data to construct the layer + */ + addLayer(layerData: LayerData) { + this._layers.put(layerData.name, new gdjs.Layer(layerData, this)); + } + + /** + * Remove a layer. All {@link gdjs.RuntimeObject} on this layer will + * be moved back to the base layer. + * @param layerName The name of the layer to remove + */ + removeLayer(layerName: string) { + const allInstances = this.getAdhocListOfAllInstances(); + for (let i = 0; i < allInstances.length; ++i) { + const runtimeObject = allInstances[i]; + if (runtimeObject.getLayer() === layerName) { + runtimeObject.setLayer(''); + } + } + this._layers.remove(layerName); + } + + /** + * Change the position of a layer. + * + * @param layerName The name of the layer to reorder + * @param index The new position in the list of layers + */ + setLayerIndex(layerName: string, index: integer): void { + const layer: gdjs.Layer = this._layers.get(layerName); + if (!layer) { + return; + } + this.getRenderer().setLayerIndex(layer, index); + } + + /** + * Fill the array passed as argument with the names of all layers + * @param result The array where to put the layer names + */ + getAllLayerNames(result: string[]) { + this._layers.keys(result); + } + + /** + * Return the number of instances of the specified object living in the container. + * @param objectName The object name for which instances must be counted. + */ + getInstancesCountOnScene(objectName: string): integer { + const instances = this._instances.get(objectName); + if (instances) { + return instances.length; + } + + return 0; + } + + /** + * Update the objects positions according to their forces + */ + updateObjectsForces(): void { + for (const name in this._instances.items) { + if (this._instances.items.hasOwnProperty(name)) { + const list = this._instances.items[name]; + for (let j = 0, listLen = list.length; j < listLen; ++j) { + const obj = list[j]; + if (!obj.hasNoForces()) { + const averageForce = obj.getAverageForce(); + const elapsedTimeInSeconds = obj.getElapsedTime() / 1000; + obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds); + obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds); + obj.updateForces(elapsedTimeInSeconds); + } + } + } + } + } + + /** + * Clear any data structures to make sure memory is freed as soon as + * possible. + */ + _destroy() { + // It should not be necessary to reset these variables, but this help + // ensuring that all memory related to the container is released immediately. + this._layers = new Hashtable(); + this._objects = new Hashtable(); + this._instances = new Hashtable(); + this._instancesCache = new Hashtable(); + this._objectsCtor = new Hashtable(); + this._allInstancesList = []; + this._instancesRemoved = []; + } + } +} diff --git a/GDJS/Runtime/RuntimeSceneLayer.ts b/GDJS/Runtime/RuntimeSceneLayer.ts new file mode 100644 index 0000000000..9aa514065c --- /dev/null +++ b/GDJS/Runtime/RuntimeSceneLayer.ts @@ -0,0 +1,74 @@ +/* + * GDevelop JS Platform + * Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved. + * This project is released under the MIT License. + */ +namespace gdjs { + /** + * Represents a layer of a scene, used to display objects. + * + * Viewports and multiple cameras are not supported. + * + * It does some optimizations but works exactly the same as + * {@link gdjs.Layer}. + */ + export class RuntimeSceneLayer extends gdjs.Layer { + /** + * @param layerData The data used to initialize the layer + * @param scene The scene in which the layer is used + */ + constructor(layerData: LayerData, scene: gdjs.RuntimeScene) { + super(layerData, scene); + } + + convertCoords( + x: float, + y: float, + cameraId: integer = 0, + result: FloatPoint + ): FloatPoint { + // The result parameter used to be optional. + let position = result || [0, 0]; + x -= this.getRuntimeScene()._cachedGameResolutionWidth / 2; + y -= this.getRuntimeScene()._cachedGameResolutionHeight / 2; + x /= Math.abs(this._zoomFactor); + y /= Math.abs(this._zoomFactor); + + // Only compute angle and cos/sin once (allow heavy optimization from JS engines). + const angleInRadians = (this._cameraRotation / 180) * Math.PI; + const tmp = x; + const cosValue = Math.cos(angleInRadians); + const sinValue = Math.sin(angleInRadians); + x = cosValue * x - sinValue * y; + y = sinValue * tmp + cosValue * y; + position[0] = x + this.getCameraX(cameraId); + position[1] = y + this.getCameraY(cameraId); + return position; + } + + convertInverseCoords( + x: float, + y: float, + cameraId: integer = 0, + result: FloatPoint + ): FloatPoint { + // The result parameter used to be optional. + let position = result || [0, 0]; + x -= this.getCameraX(cameraId); + y -= this.getCameraY(cameraId); + + // Only compute angle and cos/sin once (allow heavy optimization from JS engines). + const angleInRadians = (this._cameraRotation / 180) * Math.PI; + const tmp = x; + const cosValue = Math.cos(-angleInRadians); + const sinValue = Math.sin(-angleInRadians); + x = cosValue * x - sinValue * y; + y = sinValue * tmp + cosValue * y; + x *= Math.abs(this._zoomFactor); + y *= Math.abs(this._zoomFactor); + position[0] = x + this.getRuntimeScene()._cachedGameResolutionWidth / 2; + position[1] = y + this.getRuntimeScene()._cachedGameResolutionHeight / 2; + return position; + } + } +} diff --git a/GDJS/Runtime/debugger-client/hot-reloader.ts b/GDJS/Runtime/debugger-client/hot-reloader.ts index 6da637ca0c..e3305dd6b5 100644 --- a/GDJS/Runtime/debugger-client/hot-reloader.ts +++ b/GDJS/Runtime/debugger-client/hot-reloader.ts @@ -639,7 +639,7 @@ namespace gdjs { newObjects.forEach((newObjectData) => { const objectName = newObjectData.name; const newBehaviors = newObjectData.behaviors; - const runtimeObjects = runtimeScene.getObjects(objectName); + const runtimeObjects = runtimeScene.getObjects(objectName)!; changedRuntimeBehaviors.forEach((changedRuntimeBehavior) => { const behaviorTypeName = changedRuntimeBehavior.behaviorTypeName; @@ -784,7 +784,7 @@ namespace gdjs { runtimeScene.updateObject(newObjectData); // Update existing instances - const runtimeObjects = runtimeScene.getObjects(newObjectData.name); + const runtimeObjects = runtimeScene.getObjects(newObjectData.name)!; // Update instances state runtimeObjects.forEach((runtimeObject) => { diff --git a/GDJS/Runtime/events-tools/cameratools.ts b/GDJS/Runtime/events-tools/cameratools.ts index 276cc333eb..2bd7925eeb 100644 --- a/GDJS/Runtime/events-tools/cameratools.ts +++ b/GDJS/Runtime/events-tools/cameratools.ts @@ -7,215 +7,217 @@ namespace gdjs { export namespace evtTools { export namespace camera { export const setCameraX = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, x: float, layer: string, cameraId: integer ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - runtimeScene.getLayer(layer).setCameraX(x, cameraId); + instanceContainer.getLayer(layer).setCameraX(x, cameraId); }; export const setCameraY = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, y: float, layer: string, cameraId: integer ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - runtimeScene.getLayer(layer).setCameraY(y, cameraId); + instanceContainer.getLayer(layer).setCameraY(y, cameraId); }; export const getCameraX = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, cameraId: integer ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } - return runtimeScene.getLayer(layer).getCameraX(); + return instanceContainer.getLayer(layer).getCameraX(); }; export const getCameraY = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, cameraId: integer ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } - return runtimeScene.getLayer(layer).getCameraY(); + return instanceContainer.getLayer(layer).getCameraY(); }; export const getCameraWidth = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, cameraId: integer ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } - return runtimeScene.getLayer(layer).getCameraWidth(); + return instanceContainer.getLayer(layer).getCameraWidth(); }; export const getCameraHeight = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, cameraId: integer ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } - return runtimeScene.getLayer(layer).getCameraHeight(); + return instanceContainer.getLayer(layer).getCameraHeight(); }; export const getCameraBorderLeft = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, cameraId: integer ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } return ( - getCameraX(runtimeScene, layer, cameraId) - - getCameraWidth(runtimeScene, layer, cameraId) / 2 + getCameraX(instanceContainer, layer, cameraId) - + getCameraWidth(instanceContainer, layer, cameraId) / 2 ); }; export const getCameraBorderRight = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, cameraId: integer ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } return ( - getCameraX(runtimeScene, layer, cameraId) + - getCameraWidth(runtimeScene, layer, cameraId) / 2 + getCameraX(instanceContainer, layer, cameraId) + + getCameraWidth(instanceContainer, layer, cameraId) / 2 ); }; export const getCameraBorderTop = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, cameraId: integer ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } return ( - getCameraY(runtimeScene, layer, cameraId) - - getCameraHeight(runtimeScene, layer, cameraId) / 2 + getCameraY(instanceContainer, layer, cameraId) - + getCameraHeight(instanceContainer, layer, cameraId) / 2 ); }; export const getCameraBorderBottom = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, cameraId: integer ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } return ( - getCameraY(runtimeScene, layer, cameraId) + - getCameraHeight(runtimeScene, layer, cameraId) / 2 + getCameraY(instanceContainer, layer, cameraId) + + getCameraHeight(instanceContainer, layer, cameraId) / 2 ); }; export const showLayer = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - return runtimeScene.getLayer(layer).show(true); + return instanceContainer.getLayer(layer).show(true); }; export const hideLayer = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - return runtimeScene.getLayer(layer).show(false); + return instanceContainer.getLayer(layer).show(false); }; export const layerIsVisible = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string ): boolean { return ( - runtimeScene.hasLayer(layer) && - runtimeScene.getLayer(layer).isVisible() + instanceContainer.hasLayer(layer) && + instanceContainer.getLayer(layer).isVisible() ); }; export const setCameraRotation = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, rotation: float, layer: string, cameraId: integer ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - return runtimeScene + return instanceContainer .getLayer(layer) .setCameraRotation(rotation, cameraId); }; export const getCameraRotation = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, cameraId: integer ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } - return runtimeScene.getLayer(layer).getCameraRotation(cameraId); + return instanceContainer.getLayer(layer).getCameraRotation(cameraId); }; export const getCameraZoom = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, cameraId: integer ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } - return runtimeScene.getLayer(layer).getCameraZoom(cameraId); + return instanceContainer.getLayer(layer).getCameraZoom(cameraId); }; export const setCameraZoom = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, newZoom: float, layer: string, cameraId: integer ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - return runtimeScene.getLayer(layer).setCameraZoom(newZoom, cameraId); + return instanceContainer + .getLayer(layer) + .setCameraZoom(newZoom, cameraId); }; export const centerCamera = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, object: gdjs.RuntimeObject | null, anticipateMove: boolean, layerName: string, cameraId: integer ) { - if (!runtimeScene.hasLayer(layerName) || object == null) { + if (!instanceContainer.hasLayer(layerName) || object == null) { return; } let xOffset = 0; @@ -223,11 +225,11 @@ namespace gdjs { if (anticipateMove && !object.hasNoForces()) { const objectAverageForce = object.getAverageForce(); const elapsedTimeInSeconds = - object.getElapsedTime(runtimeScene) / 1000; + object.getElapsedTime(instanceContainer) / 1000; xOffset = objectAverageForce.getX() * elapsedTimeInSeconds; yOffset = objectAverageForce.getY() * elapsedTimeInSeconds; } - const layer = runtimeScene.getLayer(layerName); + const layer = instanceContainer.getLayer(layerName); layer.setCameraX(object.getCenterXInScene() + xOffset, cameraId); layer.setCameraY(object.getCenterYInScene() + yOffset, cameraId); }; @@ -236,7 +238,7 @@ namespace gdjs { * @deprecated prefer using centerCamera and clampCamera. */ export const centerCameraWithinLimits = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, object: gdjs.RuntimeObject | null, left: number, top: number, @@ -246,9 +248,15 @@ namespace gdjs { layerName: string, cameraId: integer ) { - centerCamera(runtimeScene, object, anticipateMove, layerName, cameraId); + centerCamera( + instanceContainer, + object, + anticipateMove, + layerName, + cameraId + ); clampCamera( - runtimeScene, + instanceContainer, left, top, right, @@ -259,7 +267,7 @@ namespace gdjs { }; export const clampCamera = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, left: float, top: float, right: float, @@ -267,10 +275,10 @@ namespace gdjs { layerName: string, cameraId: integer ) { - if (!runtimeScene.hasLayer(layerName)) { + if (!instanceContainer.hasLayer(layerName)) { return; } - const layer = runtimeScene.getLayer(layerName); + const layer = instanceContainer.getLayer(layerName); const cameraHalfWidth = layer.getCameraWidth(cameraId) / 2; const cameraHalfHeight = layer.getCameraHeight(cameraId) / 2; @@ -304,164 +312,167 @@ namespace gdjs { /** * Update a layer effect parameter (with a number). - * @param runtimeScene The scene + * @param instanceContainer the container owning the layer * @param layer The name of the layer * @param effect The name of the effect * @param parameter The parameter to update * @param value The new value */ export const setLayerEffectDoubleParameter = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, effect: string, parameter: string, value: float ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - return runtimeScene + return instanceContainer .getLayer(layer) .setEffectDoubleParameter(effect, parameter, value); }; /** * Update a layer effect parameter (with a string). - * @param runtimeScene The scene + * @param instanceContainer the container owning the layer * @param layer The name of the layer * @param effect The name of the effect * @param parameter The parameter to update * @param value The new value */ export const setLayerEffectStringParameter = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, effect: string, parameter: string, value: string ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - return runtimeScene + return instanceContainer .getLayer(layer) .setEffectStringParameter(effect, parameter, value); }; /** * Enable or disable a layer effect parameter (boolean). - * @param runtimeScene The scene + * @param instanceContainer the container owning the layer * @param layer The name of the layer * @param effect The name of the effect * @param parameter The parameter to update * @param value The new value */ export const setLayerEffectBooleanParameter = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, effect: string, parameter: string, value: boolean ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - return runtimeScene + return instanceContainer .getLayer(layer) .setEffectBooleanParameter(effect, parameter, value); }; /** * Enable, or disable, an effect of a layer. - * @param runtimeScene The scene + * @param instanceContainer the container owning the layer * @param layer The name of the layer * @param effect The name of the effect * @param enabled true to enable, false to disable. */ export const enableLayerEffect = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, effect: string, enabled: boolean ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - runtimeScene.getLayer(layer).enableEffect(effect, enabled); + instanceContainer.getLayer(layer).enableEffect(effect, enabled); }; /** * Check if an effect is enabled. - * @param runtimeScene The scene + * @param instanceContainer the container owning the layer * @param layer The name of the layer * @param effect The name of the effect * @return true if the effect is enabled, false otherwise. */ export const layerEffectEnabled = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, effect: string ): boolean { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return true; } - return runtimeScene.getLayer(layer).isEffectEnabled(effect); + return instanceContainer.getLayer(layer).isEffectEnabled(effect); }; export const setLayerTimeScale = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, timeScale: float ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - return runtimeScene.getLayer(layer).setTimeScale(timeScale); + return instanceContainer.getLayer(layer).setTimeScale(timeScale); }; export const getLayerTimeScale = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 1; } - return runtimeScene.getLayer(layer).getTimeScale(); + return instanceContainer.getLayer(layer).getTimeScale(); }; export const setLayerDefaultZOrder = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string, defaultZOrder: integer ) { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return; } - return runtimeScene.getLayer(layer).setDefaultZOrder(defaultZOrder); + return instanceContainer + .getLayer(layer) + .setDefaultZOrder(defaultZOrder); }; export const getLayerDefaultZOrder = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layer: string ): number { - if (!runtimeScene.hasLayer(layer)) { + if (!instanceContainer.hasLayer(layer)) { return 0; } - return runtimeScene.getLayer(layer).getDefaultZOrder(); + return instanceContainer.getLayer(layer).getDefaultZOrder(); }; /** + * @param instanceContainer the container owning the layer * @param layerName The lighting layer with the ambient color. * @param rgbColor The color, in RGB format ("128;200;255"). */ export const setLayerAmbientLightColor = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, layerName: string, rgbColor: string ) { if ( - !runtimeScene.hasLayer(layerName) || - !runtimeScene.getLayer(layerName).isLightingLayer() + !instanceContainer.hasLayer(layerName) || + !instanceContainer.getLayer(layerName).isLightingLayer() ) { return; } @@ -469,7 +480,7 @@ namespace gdjs { if (colors.length < 3) { return; } - return runtimeScene + return instanceContainer .getLayer(layerName) .setClearColor( parseInt(colors[0], 10), diff --git a/GDJS/Runtime/events-tools/inputtools.ts b/GDJS/Runtime/events-tools/inputtools.ts index 5d13b710e9..40e4ac7427 100644 --- a/GDJS/Runtime/events-tools/inputtools.ts +++ b/GDJS/Runtime/events-tools/inputtools.ts @@ -144,9 +144,12 @@ namespace gdjs { * Return true if the specified key is pressed * */ - export const isKeyPressed = function (runtimeScene, key) { + export const isKeyPressed = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + key: string + ) { if (gdjs.evtTools.input.keysNameToCode.hasOwnProperty(key)) { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .isKeyPressed(gdjs.evtTools.input.keysNameToCode[key]); @@ -158,9 +161,12 @@ namespace gdjs { * Return true if the specified key was just released * */ - export const wasKeyReleased = function (runtimeScene, key) { + export const wasKeyReleased = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + key: string + ) { if (gdjs.evtTools.input.keysNameToCode.hasOwnProperty(key)) { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .wasKeyReleased(gdjs.evtTools.input.keysNameToCode[key]); @@ -171,8 +177,10 @@ namespace gdjs { /** * Return the name of the last key pressed in the game */ - export const lastPressedKey = function (runtimeScene) { - const keyCode = runtimeScene + export const lastPressedKey = function ( + instanceContainer: gdjs.RuntimeInstanceContainer + ) { + const keyCode = instanceContainer .getGame() .getInputManager() .getLastPressedKey(); @@ -182,29 +190,36 @@ namespace gdjs { return ''; }; - export const anyKeyPressed = function (runtimeScene) { - return runtimeScene.getGame().getInputManager().anyKeyPressed(); + export const anyKeyPressed = function ( + instanceContainer: gdjs.RuntimeInstanceContainer + ) { + return instanceContainer.getGame().getInputManager().anyKeyPressed(); }; - export const anyKeyReleased = function (runtimeScene) { - return runtimeScene.getGame().getInputManager().anyKeyReleased(); + export const anyKeyReleased = function ( + instanceContainer: gdjs.RuntimeInstanceContainer + ) { + return instanceContainer.getGame().getInputManager().anyKeyReleased(); }; - export const isMouseButtonPressed = function (runtimeScene, button) { + export const isMouseButtonPressed = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + button: string + ) { if (button === 'Left') { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .isMouseButtonPressed(0); } if (button === 'Right') { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .isMouseButtonPressed(1); } if (button === 'Middle') { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .isMouseButtonPressed(2); @@ -212,21 +227,24 @@ namespace gdjs { return false; }; - export const isMouseButtonReleased = function (runtimeScene, button) { + export const isMouseButtonReleased = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + button: string + ) { if (button === 'Left') { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .isMouseButtonReleased(0); } if (button === 'Right') { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .isMouseButtonReleased(1); } if (button === 'Middle') { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .isMouseButtonReleased(2); @@ -234,129 +252,178 @@ namespace gdjs { return false; }; - export const hideCursor = function (runtimeScene) { - runtimeScene.getRenderer().hideCursor(); + export const hideCursor = function ( + instanceContainer: gdjs.RuntimeScene + ) { + instanceContainer.getScene().getRenderer().hideCursor(); }; - export const showCursor = function (runtimeScene) { - runtimeScene.getRenderer().showCursor(); + export const showCursor = function ( + instanceContainer: gdjs.RuntimeScene + ) { + instanceContainer.getScene().getRenderer().showCursor(); }; - export const getMouseWheelDelta = function (runtimeScene) { - return runtimeScene.getGame().getInputManager().getMouseWheelDelta(); + export const getMouseWheelDelta = function ( + instanceContainer: gdjs.RuntimeInstanceContainer + ) { + return instanceContainer + .getGame() + .getInputManager() + .getMouseWheelDelta(); }; - export const isScrollingUp = function (runtimeScene) { - return runtimeScene.getGame().getInputManager().isScrollingUp(); + export const isScrollingUp = function ( + instanceContainer: gdjs.RuntimeInstanceContainer + ) { + return instanceContainer.getGame().getInputManager().isScrollingUp(); }; - export const isScrollingDown = function (runtimeScene) { - return runtimeScene.getGame().getInputManager().isScrollingDown(); + export const isScrollingDown = function ( + instanceContainer: gdjs.RuntimeInstanceContainer + ) { + return instanceContainer.getGame().getInputManager().isScrollingDown(); }; - export const getMouseX = function (runtimeScene, layer, camera) { - return runtimeScene + export const getMouseX = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + layer: string, + camera: integer + ) { + const workingPoint: FloatPoint = gdjs.staticArray( + gdjs.evtTools.input.getMouseX + ) as FloatPoint; + return instanceContainer .getLayer(layer) .convertCoords( - runtimeScene.getGame().getInputManager().getMouseX(), - runtimeScene.getGame().getInputManager().getMouseY() + instanceContainer.getGame().getInputManager().getMouseX(), + instanceContainer.getGame().getInputManager().getMouseY(), + 0, + workingPoint )[0]; }; - export const getMouseY = function (runtimeScene, layer, camera) { - return runtimeScene + export const getMouseY = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + layer: string, + camera: integer + ) { + const workingPoint: FloatPoint = gdjs.staticArray( + gdjs.evtTools.input.getMouseY + ) as FloatPoint; + return instanceContainer .getLayer(layer) .convertCoords( - runtimeScene.getGame().getInputManager().getMouseX(), - runtimeScene.getGame().getInputManager().getMouseY() + instanceContainer.getGame().getInputManager().getMouseX(), + instanceContainer.getGame().getInputManager().getMouseY(), + 0, + workingPoint )[1]; }; export const isMouseInsideCanvas = function ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { - return runtimeScene.getGame().getInputManager().isMouseInsideCanvas(); + return instanceContainer + .getGame() + .getInputManager() + .isMouseInsideCanvas(); }; - export const _cursorIsOnObject = function (obj, runtimeScene) { - return obj.cursorOnObject(runtimeScene); + export const _cursorIsOnObject = function ( + obj: gdjs.RuntimeObject, + instanceContainer: gdjs.RuntimeInstanceContainer + ) { + return obj.cursorOnObject(instanceContainer); }; export const cursorOnObject = function ( - objectsLists, - runtimeScene, - accurate, - inverted + objectsLists: Hashtable, + instanceContainer: gdjs.RuntimeInstanceContainer, + accurate: boolean, + inverted: boolean ) { return gdjs.evtTools.object.pickObjectsIf( gdjs.evtTools.input._cursorIsOnObject, objectsLists, inverted, - runtimeScene + instanceContainer ); }; export const getTouchX = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, identifier: integer, layer: string, camera: integer ) { - return runtimeScene + const workingPoint: FloatPoint = gdjs.staticArray( + gdjs.evtTools.input.getTouchX + ) as FloatPoint; + return instanceContainer .getLayer(layer) .convertCoords( - runtimeScene.getGame().getInputManager().getTouchX(identifier), - runtimeScene.getGame().getInputManager().getTouchY(identifier) + instanceContainer.getGame().getInputManager().getTouchX(identifier), + instanceContainer.getGame().getInputManager().getTouchY(identifier), + 0, + workingPoint )[0]; }; export const getTouchY = ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, identifier: integer, layer: string, camera: integer ) => { - return runtimeScene + const workingPoint: FloatPoint = gdjs.staticArray( + gdjs.evtTools.input.getTouchY + ) as FloatPoint; + return instanceContainer .getLayer(layer) .convertCoords( - runtimeScene.getGame().getInputManager().getTouchX(identifier), - runtimeScene.getGame().getInputManager().getTouchY(identifier) + instanceContainer.getGame().getInputManager().getTouchX(identifier), + instanceContainer.getGame().getInputManager().getTouchY(identifier), + 0, + workingPoint )[1]; }; export const hasAnyTouchStarted = ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): boolean => { return ( - runtimeScene.getGame().getInputManager().getStartedTouchIdentifiers() - .length > 0 + instanceContainer + .getGame() + .getInputManager() + .getStartedTouchIdentifiers().length > 0 ); }; export const getStartedTouchCount = ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ): integer => { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .getStartedTouchIdentifiers().length; }; export const getStartedTouchIdentifier = ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, index: integer ): integer => { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .getStartedTouchIdentifiers()[index]; }; export const hasTouchEnded = ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, identifier: integer ): boolean => { - return runtimeScene + return instanceContainer .getGame() .getInputManager() .hasTouchEnded(identifier); @@ -380,9 +447,9 @@ namespace gdjs { * @deprecated */ export const popStartedTouch = function ( - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { - const startedTouchId = runtimeScene + const startedTouchId = instanceContainer .getGame() .getInputManager() .popStartedTouch(); @@ -396,8 +463,10 @@ namespace gdjs { /** * @deprecated */ - export const popEndedTouch = function (runtimeScene: gdjs.RuntimeScene) { - const endedTouchId = runtimeScene + export const popEndedTouch = function ( + instanceContainer: gdjs.RuntimeInstanceContainer + ) { + const endedTouchId = instanceContainer .getGame() .getInputManager() .popEndedTouch(); @@ -408,8 +477,14 @@ namespace gdjs { return false; }; - export const touchSimulateMouse = function (runtimeScene, enable) { - runtimeScene.getGame().getInputManager().touchSimulateMouse(enable); + export const touchSimulateMouse = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + enable: boolean + ) { + instanceContainer + .getGame() + .getInputManager() + .touchSimulateMouse(enable); }; } } diff --git a/GDJS/Runtime/events-tools/networktools.ts b/GDJS/Runtime/events-tools/networktools.ts index ee20bd1642..9badd9559a 100644 --- a/GDJS/Runtime/events-tools/networktools.ts +++ b/GDJS/Runtime/events-tools/networktools.ts @@ -147,10 +147,10 @@ namespace gdjs { }; export const enableMetrics = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, enable: boolean ) { - runtimeScene.getGame().enableMetrics(enable); + instanceContainer.getGame().enableMetrics(enable); }; /** diff --git a/GDJS/Runtime/events-tools/objecttools.ts b/GDJS/Runtime/events-tools/objecttools.ts index 3b2cfa5b48..29713e4c4f 100644 --- a/GDJS/Runtime/events-tools/objecttools.ts +++ b/GDJS/Runtime/events-tools/objecttools.ts @@ -13,7 +13,7 @@ namespace gdjs { * @param runtimeObject The object to keep in the lists */ export const pickOnly = function ( - objectsLists: Hashtable>, + objectsLists: ObjectsLists, runtimeObject: gdjs.RuntimeObject ) { for (const listName in objectsLists.items) { @@ -36,7 +36,7 @@ namespace gdjs { /** * Do a test on two tables of objects so as to pick only the pair of objects for which the test is true. * - * Note that the predicate method is not called stricly for each pair: When considering a pair of objects, if + * Note that the predicate method is not called strictly for each pair: When considering a pair of objects, if * these objects have already been marked as picked, the predicate method won't be called again. * * Cost (Worst case, predicate being always false): @@ -63,8 +63,8 @@ namespace gdjs { object2: gdjs.RuntimeObject, extraArg: any ) => boolean, - objectsLists1: Hashtable>, - objectsLists2: Hashtable>, + objectsLists1: ObjectsLists, + objectsLists2: ObjectsLists, inverted: boolean, extraArg: any ) { @@ -170,7 +170,7 @@ namespace gdjs { */ export const pickObjectsIf = function ( predicate: Function, - objectsLists: Hashtable>, + objectsLists: ObjectsLists, negatePredicate: boolean, extraArg: any ): boolean { @@ -219,10 +219,10 @@ namespace gdjs { }; export const hitBoxesCollisionTest = function ( - objectsLists1: Hashtable>, - objectsLists2: Hashtable>, + objectsLists1: ObjectsLists, + objectsLists2: ObjectsLists, inverted: boolean, - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, ignoreTouchingEdges: boolean ) { return gdjs.evtTools.object.twoListsTest( @@ -239,8 +239,8 @@ namespace gdjs { }; export const distanceTest = function ( - objectsLists1: Hashtable>, - objectsLists2: Hashtable>, + objectsLists1: ObjectsLists, + objectsLists2: ObjectsLists, distance: float, inverted: boolean ) { @@ -278,8 +278,8 @@ namespace gdjs { }; export const movesTowardTest = function ( - objectsLists1: Hashtable>, - objectsLists2: Hashtable>, + objectsLists1: ObjectsLists, + objectsLists2: ObjectsLists, tolerance: float, inverted: boolean ) { @@ -337,7 +337,10 @@ namespace gdjs { return true; }; - export const pickRandomObject = function (runtimeScene, objectsLists) { + export const pickRandomObject = function ( + instanceContainer: gdjs.RuntimeInstanceContainer, + objectsLists: ObjectsLists + ) { // Compute one many objects we have let objectsCount = 0; for (let listName in objectsLists.items) { @@ -403,7 +406,7 @@ namespace gdjs { }; export const raycastObject = function ( - objectsLists: Hashtable>, + objectsLists: ObjectsLists, x: float, y: float, angle: float, @@ -425,7 +428,7 @@ namespace gdjs { }; export const raycastObjectToPosition = function ( - objectsLists: Hashtable>, + objectsLists: ObjectsLists, x: float, y: float, endX: float, @@ -481,9 +484,9 @@ namespace gdjs { export const doCreateObjectOnScene = function ( objectsContext: EventsFunctionContext | gdjs.RuntimeScene, objectName: string, - objectsLists: Hashtable>, - x, - y, + objectsLists: ObjectsLists, + x: float, + y: float, layerName: string ) { // objectsContext will either be the gdjs.RuntimeScene or, in an events function, the @@ -509,19 +512,19 @@ namespace gdjs { * Allows events to create a new object on a scene. */ export const createObjectOnScene = function ( - objectsContext, - objectsLists, - x, - y, - layer + objectsContext: EventsFunctionContext | gdjs.RuntimeScene, + objectsLists: ObjectsLists, + x: float, + y: float, + layerName: string ) { gdjs.evtTools.object.doCreateObjectOnScene( objectsContext, - objectsLists.firstKey(), + objectsLists.firstKey() as string, objectsLists, x, y, - layer + layerName ); }; @@ -529,12 +532,12 @@ namespace gdjs { * Allows events to create a new object on a scene. */ export const createObjectFromGroupOnScene = function ( - objectsContext, - objectsLists, - objectName, - x, - y, - layer + objectsContext: EventsFunctionContext | gdjs.RuntimeScene, + objectsLists: ObjectsLists, + objectName: string, + x: float, + y: float, + layerName: string ) { gdjs.evtTools.object.doCreateObjectOnScene( objectsContext, @@ -542,7 +545,7 @@ namespace gdjs { objectsLists, x, y, - layer + layerName ); }; diff --git a/GDJS/Runtime/events-tools/runtimescenetools.ts b/GDJS/Runtime/events-tools/runtimescenetools.ts index 166c365e8e..7411ca7ff3 100644 --- a/GDJS/Runtime/events-tools/runtimescenetools.ts +++ b/GDJS/Runtime/events-tools/runtimescenetools.ts @@ -6,40 +6,54 @@ namespace gdjs { export namespace evtTools { export namespace runtimeScene { - export const sceneJustBegins = function (runtimeScene) { - return runtimeScene.getTimeManager().isFirstFrame(); + export const sceneJustBegins = function ( + runtimeScene: gdjs.RuntimeScene + ) { + return runtimeScene.getScene().getTimeManager().isFirstFrame(); }; - export const sceneJustResumed = function (runtimeScene) { - return runtimeScene.sceneJustResumed(); + export const sceneJustResumed = function ( + runtimeScene: gdjs.RuntimeScene + ) { + return runtimeScene.getScene().sceneJustResumed(); }; - export const getSceneName = function (runtimeScene) { - return runtimeScene.getName(); + export const getSceneName = function (runtimeScene: gdjs.RuntimeScene) { + return runtimeScene.getScene().getName(); }; - export const setBackgroundColor = function (runtimeScene, rgbColor) { + export const setBackgroundColor = function ( + runtimeScene: gdjs.RuntimeScene, + rgbColor: string + ) { const colors = rgbColor.split(';'); if (colors.length < 3) { return; } - runtimeScene.setBackgroundColor( - parseInt(colors[0]), - parseInt(colors[1]), - parseInt(colors[2]) - ); + runtimeScene + .getScene() + .setBackgroundColor( + parseInt(colors[0]), + parseInt(colors[1]), + parseInt(colors[2]) + ); }; - export const getElapsedTimeInSeconds = function (runtimeScene) { - return runtimeScene.getTimeManager().getElapsedTime() / 1000; + export const getElapsedTimeInSeconds = function ( + runtimeScene: gdjs.RuntimeScene + ) { + return runtimeScene.getScene().getTimeManager().getElapsedTime() / 1000; }; - export const setTimeScale = function (runtimeScene, timeScale) { - return runtimeScene.getTimeManager().setTimeScale(timeScale); + export const setTimeScale = function ( + runtimeScene: gdjs.RuntimeScene, + timeScale: float + ) { + return runtimeScene.getScene().getTimeManager().setTimeScale(timeScale); }; - export const getTimeScale = function (runtimeScene) { - return runtimeScene.getTimeManager().getTimeScale(); + export const getTimeScale = function (runtimeScene: gdjs.RuntimeScene) { + return runtimeScene.getScene().getTimeManager().getTimeScale(); }; /** @@ -57,7 +71,7 @@ namespace gdjs { timeInSeconds: float, timerName: string ) { - const timeManager = runtimeScene.getTimeManager(); + const timeManager = runtimeScene.getScene().getTimeManager(); if (!timeManager.hasTimer(timerName)) { timeManager.addTimer(timerName); return false; @@ -67,16 +81,22 @@ namespace gdjs { ); }; - export const timerPaused = function (runtimeScene, timerName) { - const timeManager = runtimeScene.getTimeManager(); + export const timerPaused = function ( + runtimeScene: gdjs.RuntimeScene, + timerName: string + ) { + const timeManager = runtimeScene.getScene().getTimeManager(); if (!timeManager.hasTimer(timerName)) { return false; } return timeManager.getTimer(timerName).isPaused(); }; - export const resetTimer = function (runtimeScene, timerName) { - const timeManager = runtimeScene.getTimeManager(); + export const resetTimer = function ( + runtimeScene: gdjs.RuntimeScene, + timerName: string + ) { + const timeManager = runtimeScene.getScene().getTimeManager(); if (!timeManager.hasTimer(timerName)) { timeManager.addTimer(timerName); } else { @@ -84,24 +104,33 @@ namespace gdjs { } }; - export const pauseTimer = function (runtimeScene, timerName) { - const timeManager = runtimeScene.getTimeManager(); + export const pauseTimer = function ( + runtimeScene: gdjs.RuntimeScene, + timerName: string + ) { + const timeManager = runtimeScene.getScene().getTimeManager(); if (!timeManager.hasTimer(timerName)) { timeManager.addTimer(timerName); } timeManager.getTimer(timerName).setPaused(true); }; - export const unpauseTimer = function (runtimeScene, timerName) { - const timeManager = runtimeScene.getTimeManager(); + export const unpauseTimer = function ( + runtimeScene: gdjs.RuntimeScene, + timerName: string + ) { + const timeManager = runtimeScene.getScene().getTimeManager(); if (!timeManager.hasTimer(timerName)) { timeManager.addTimer(timerName); } return timeManager.getTimer(timerName).setPaused(false); }; - export const removeTimer = function (runtimeScene, timerName) { - const timeManager = runtimeScene.getTimeManager(); + export const removeTimer = function ( + runtimeScene: gdjs.RuntimeScene, + timerName: string + ) { + const timeManager = runtimeScene.getScene().getTimeManager(); timeManager.removeTimer(timerName); }; @@ -116,6 +145,7 @@ namespace gdjs { update(runtimeScene: RuntimeScene): boolean { this.timeElapsedOnScene += runtimeScene + .getScene() .getTimeManager() .getElapsedTime(); return this.timeElapsedOnScene >= this.duration; @@ -139,7 +169,7 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, timerName: string ) { - const timeManager = runtimeScene.getTimeManager(); + const timeManager = runtimeScene.getScene().getTimeManager(); if (!timeManager.hasTimer(timerName)) { return 0; } @@ -158,7 +188,7 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, timerName: string ) { - const timeManager = runtimeScene.getTimeManager(); + const timeManager = runtimeScene.getScene().getTimeManager(); if (!timeManager.hasTimer(timerName)) { return Number.NaN; } @@ -168,7 +198,9 @@ namespace gdjs { export const getTimeFromStartInSeconds = function ( runtimeScene: gdjs.RuntimeScene ) { - return runtimeScene.getTimeManager().getTimeFromStart() / 1000; + return ( + runtimeScene.getScene().getTimeManager().getTimeFromStart() / 1000 + ); }; export const getTime = function ( @@ -211,34 +243,42 @@ namespace gdjs { if (!runtimeScene.getGame().getSceneData(newSceneName)) { return; } - runtimeScene.requestChange( - clearOthers - ? gdjs.SceneChangeRequest.CLEAR_SCENES - : gdjs.SceneChangeRequest.REPLACE_SCENE, - newSceneName - ); + runtimeScene + .getScene() + .requestChange( + clearOthers + ? gdjs.SceneChangeRequest.CLEAR_SCENES + : gdjs.SceneChangeRequest.REPLACE_SCENE, + newSceneName + ); }; - export const pushScene = function (runtimeScene, newSceneName) { + export const pushScene = function ( + runtimeScene: gdjs.RuntimeScene, + newSceneName: string + ) { if (!runtimeScene.getGame().getSceneData(newSceneName)) { return; } - runtimeScene.requestChange( - gdjs.SceneChangeRequest.PUSH_SCENE, - newSceneName - ); + runtimeScene + .getScene() + .requestChange(gdjs.SceneChangeRequest.PUSH_SCENE, newSceneName); }; - export const popScene = function (runtimeScene) { - runtimeScene.requestChange(gdjs.SceneChangeRequest.POP_SCENE); + export const popScene = function (runtimeScene: gdjs.RuntimeScene) { + runtimeScene + .getScene() + .requestChange(gdjs.SceneChangeRequest.POP_SCENE); }; - export const stopGame = function (runtimeScene) { - runtimeScene.requestChange(gdjs.SceneChangeRequest.STOP_GAME); + export const stopGame = function (runtimeScene: gdjs.RuntimeScene) { + runtimeScene + .getScene() + .requestChange(gdjs.SceneChangeRequest.STOP_GAME); }; export const createObjectsFromExternalLayout = function ( - scene: gdjs.RuntimeScene, + scene: gdjs.RuntimeInstanceContainer, externalLayout: string, xPos: float, yPos: float @@ -252,7 +292,7 @@ namespace gdjs { // trackByPersistentUuid is set to false as we don't want external layouts // instantiated at runtime to be hot-reloaded. - scene.createObjectsFrom( + scene.getScene().createObjectsFrom( externalLayoutData.instances, xPos, yPos, diff --git a/GDJS/Runtime/events-tools/soundtools.ts b/GDJS/Runtime/events-tools/soundtools.ts index ca33161676..6902fd36ea 100644 --- a/GDJS/Runtime/events-tools/soundtools.ts +++ b/GDJS/Runtime/events-tools/soundtools.ts @@ -11,20 +11,20 @@ namespace gdjs { export const getGlobalVolume = function ( runtimeScene: gdjs.RuntimeScene ): float { - return runtimeScene.getSoundManager().getGlobalVolume(); + return runtimeScene.getScene().getSoundManager().getGlobalVolume(); }; export const setGlobalVolume = function ( runtimeScene: gdjs.RuntimeScene, globalVolume: float ): void { - runtimeScene.getSoundManager().setGlobalVolume(globalVolume); + runtimeScene.getScene().getSoundManager().setGlobalVolume(globalVolume); }; export const unloadAllAudio = function ( runtimeScene: gdjs.RuntimeScene ): void { - runtimeScene.getSoundManager().unloadAll(); + runtimeScene.getScene().getSoundManager().unloadAll(); }; // Sounds: @@ -36,6 +36,7 @@ namespace gdjs { pitch: float ): void { runtimeScene + .getScene() .getSoundManager() .playSound(soundFile, loop, volume, pitch); }; @@ -49,6 +50,7 @@ namespace gdjs { pitch: float ): void { runtimeScene + .getScene() .getSoundManager() .playSoundOnChannel(soundFile, channel, loop, volume, pitch); }; @@ -57,7 +59,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): void { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) sound.stop(); else { logger.error(`Cannot stop non-existing sound on channel ${channel}.`); @@ -68,7 +73,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): void { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) sound.pause(); else { logger.error( @@ -81,7 +89,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): void { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) { if (!sound.playing()) sound.play(); } else { @@ -95,7 +106,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): boolean { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); return sound ? sound.playing() : false; }; @@ -103,7 +117,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): boolean { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) return sound.paused(); else { logger.error( @@ -117,7 +134,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): boolean { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) return sound.stopped(); else { logger.error( @@ -131,7 +151,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): float { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) return sound.getVolume() * 100; else { logger.error( @@ -146,7 +169,10 @@ namespace gdjs { channel: integer, volume: float ): void { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) sound.setVolume(volume / 100); else { logger.error( @@ -159,7 +185,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): float { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) return sound.getSeek(); else { logger.error( @@ -174,7 +203,10 @@ namespace gdjs { channel: integer, playingOffset: float ): void { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) sound.setSeek(playingOffset); else { logger.error( @@ -187,7 +219,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): float { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) return sound.getRate(); else { logger.error( @@ -202,7 +237,10 @@ namespace gdjs { channel: integer, pitch: float ): void { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) sound.setRate(pitch); else { logger.error( @@ -216,6 +254,7 @@ namespace gdjs { soundFile: string ) => runtimeScene + .getScene() .getSoundManager() .loadAudio(soundFile, /* isMusic= */ false); @@ -224,6 +263,7 @@ namespace gdjs { soundFile: string ) => runtimeScene + .getScene() .getSoundManager() .unloadAudio(soundFile, /* isMusic= */ false); @@ -236,6 +276,7 @@ namespace gdjs { pitch: float ): void { runtimeScene + .getScene() .getSoundManager() .playMusic(soundFile, loop, volume, pitch); }; @@ -249,6 +290,7 @@ namespace gdjs { pitch: float ): void { runtimeScene + .getScene() .getSoundManager() .playMusicOnChannel(soundFile, channel, loop, volume, pitch); }; @@ -257,7 +299,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): void { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) music.stop(); else { logger.error( @@ -270,7 +315,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): void { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) music.pause(); else { logger.error( @@ -283,7 +331,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): void { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) { if (!music.playing()) music.play(); } else { @@ -297,7 +348,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): boolean { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); return music ? music.playing() : false; }; @@ -305,7 +359,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): boolean { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) return music.paused(); else { logger.error( @@ -319,7 +376,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): boolean { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) return music.stopped(); else { logger.error( @@ -333,7 +393,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): float { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) return music.getVolume() * 100; else { logger.error( @@ -348,7 +411,10 @@ namespace gdjs { channel: integer, volume: float ): void { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) music.setVolume(volume / 100); else { logger.error( @@ -361,7 +427,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): float { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) return music.getSeek(); else { logger.error( @@ -376,7 +445,10 @@ namespace gdjs { channel: integer, playingOffset: float ): void { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) music.setSeek(playingOffset); else { logger.error( @@ -389,7 +461,10 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, channel: integer ): float { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) return music.getRate(); else { logger.error( @@ -404,7 +479,10 @@ namespace gdjs { channel: integer, pitch: float ): void { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) music.setRate(pitch); else { logger.error( @@ -418,6 +496,7 @@ namespace gdjs { soundFile: string ) => runtimeScene + .getScene() .getSoundManager() .loadAudio(soundFile, /* isMusic= */ true); @@ -426,6 +505,7 @@ namespace gdjs { soundFile: string ) => runtimeScene + .getScene() .getSoundManager() .unloadAudio(soundFile, /* isMusic= */ true); @@ -435,7 +515,10 @@ namespace gdjs { toVolume: float, timeOfFade: float /* in seconds */ ) => { - const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel); + const sound = runtimeScene + .getScene() + .getSoundManager() + .getSoundOnChannel(channel); if (sound) { sound.fade(sound.getVolume(), toVolume / 100, timeOfFade * 1000); } else { @@ -450,7 +533,10 @@ namespace gdjs { toVolume: float, timeOfFade: float /* in seconds */ ) => { - const music = runtimeScene.getSoundManager().getMusicOnChannel(channel); + const music = runtimeScene + .getScene() + .getSoundManager() + .getMusicOnChannel(channel); if (music) { music.fade(music.getVolume(), toVolume / 100, timeOfFade * 1000); } else { diff --git a/GDJS/Runtime/events-tools/storagetools.ts b/GDJS/Runtime/events-tools/storagetools.ts index e8560f35f6..d61394d79c 100644 --- a/GDJS/Runtime/events-tools/storagetools.ts +++ b/GDJS/Runtime/events-tools/storagetools.ts @@ -224,7 +224,7 @@ namespace gdjs { export const readNumberFromJSONFile = ( name: string, elementPath: string, - runtimeScene: gdjs.RuntimeScene | null, + instanceContainer: gdjs.RuntimeInstanceContainer | null, variable: gdjs.Variable ) => { return loadObject(name, (jsObject) => { @@ -250,7 +250,7 @@ namespace gdjs { export const readStringFromJSONFile = ( name: string, elementPath: string, - runtimeScene: gdjs.RuntimeScene | null, + instanceContainer: gdjs.RuntimeInstanceContainer | null, variable: gdjs.Variable ) => { return loadObject(name, (jsObject) => { diff --git a/GDJS/Runtime/events-tools/stringtools.ts b/GDJS/Runtime/events-tools/stringtools.ts index 5531f248c8..4bf1fde67e 100644 --- a/GDJS/Runtime/events-tools/stringtools.ts +++ b/GDJS/Runtime/events-tools/stringtools.ts @@ -26,21 +26,25 @@ namespace gdjs { /** * Return the uppercased version of the string. */ - export const toUpperCase = function (str) { + export const toUpperCase = function (str: string) { return str.toUpperCase(); }; /** * Return the lowercased version of the string. */ - export const toLowerCase = function (str) { + export const toLowerCase = function (str: string) { return str.toLowerCase(); }; /** * Return a new string containing the substring of the original string. */ - export const subStr = function (str, start, len) { + export const subStr = function ( + str: string, + start: integer, + len: integer + ) { if (start < str.length && start >= 0) { return str.substr(start, len); } @@ -50,7 +54,7 @@ namespace gdjs { /** * Return a new string containing the character at the specified position. */ - export const strAt = function (str, start) { + export const strAt = function (str: string, start: integer) { if (start < str.length && start >= 0) { return str.substr(start, 1); } @@ -60,7 +64,7 @@ namespace gdjs { /** * Return the string repeated. */ - export const strRepeat = function (str, count) { + export const strRepeat = function (str: string, count: integer) { let result = ''; for (let i = 0; i < count; i++) { result += str; @@ -71,21 +75,21 @@ namespace gdjs { /** * Return the length of the string */ - export const strLen = function (str) { + export const strLen = function (str: string) { return str.length; }; /** - * Search the first occurence in a string (return the position of the result, from the beginning of the string, or -1 if not found) + * Search the first occurrence in a string (return the position of the result, from the beginning of the string, or -1 if not found) */ - export const strFind = function (str, what) { + export const strFind = function (str: string, what: string) { return str.indexOf(what); }; /** - * Search the last occurence in a string (return the position of the result, from the beginning of the string, or -1 if not found) + * Search the last occurrence in a string (return the position of the result, from the beginning of the string, or -1 if not found) */ - export const strFindLast = function (str, what) { + export const strFindLast = function (str: string, what: string) { return str.lastIndexOf(what); }; @@ -95,16 +99,24 @@ namespace gdjs { export const strRFind = gdjs.evtTools.string.strFindLast; /** - * Search the first occurence in a string, starting from a specified position (return the position of the result, from the beginning of the string, or -1 if not found) + * Search the first occurrence in a string, starting from a specified position (return the position of the result, from the beginning of the string, or -1 if not found) */ - export const strFindFrom = function (str, what, pos) { + export const strFindFrom = function ( + str: string, + what: string, + pos: integer + ) { return str.indexOf(what, pos); }; /** - * Search the last occurence in a string, starting from a specified position (return the position of the result, from the beginning of the string, or -1 if not found) + * Search the last occurrence in a string, starting from a specified position (return the position of the result, from the beginning of the string, or -1 if not found) */ - export const strFindLastFrom = function (str, what, pos) { + export const strFindLastFrom = function ( + str: string, + what: string, + pos: integer + ) { return str.lastIndexOf(what, pos); }; diff --git a/GDJS/Runtime/events-tools/variabletools.ts b/GDJS/Runtime/events-tools/variabletools.ts index 956dd8e3f5..28161ca180 100644 --- a/GDJS/Runtime/events-tools/variabletools.ts +++ b/GDJS/Runtime/events-tools/variabletools.ts @@ -76,7 +76,7 @@ namespace gdjs { runtimeScene: gdjs.RuntimeScene, variableName: string ): boolean { - return runtimeScene.getVariables().has(variableName); + return runtimeScene.getScene().getVariables().has(variableName); }; /** @@ -85,10 +85,10 @@ namespace gdjs { * @private */ export const globalVariableExists = function ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, variableName: string ): boolean { - return runtimeScene.getGame().getVariables().has(variableName); + return instanceContainer.getGame().getVariables().has(variableName); }; /** diff --git a/GDJS/Runtime/gd.ts b/GDJS/Runtime/gd.ts index 279bcf157c..8da72a3c67 100644 --- a/GDJS/Runtime/gd.ts +++ b/GDJS/Runtime/gd.ts @@ -26,7 +26,7 @@ namespace gdjs { type RuntimeSceneCallback = (runtimeScene: gdjs.RuntimeScene) => void; type RuntimeSceneRuntimeObjectCallback = ( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, runtimeObject: gdjs.RuntimeObject ) => void; diff --git a/GDJS/Runtime/layer.ts b/GDJS/Runtime/layer.ts index 84195f8a35..c5584be7ef 100644 --- a/GDJS/Runtime/layer.ts +++ b/GDJS/Runtime/layer.ts @@ -5,7 +5,7 @@ */ namespace gdjs { /** - * Represents a layer of a scene, used to display objects. + * Represents a layer of a container, used to display objects. * * Viewports and multiple cameras are not supported. */ @@ -19,10 +19,8 @@ namespace gdjs { _initialEffectsData: Array; _cameraX: float; _cameraY: float; - _cachedGameResolutionWidth: integer; - _cachedGameResolutionHeight: integer; - _runtimeScene: gdjs.RuntimeScene; + _runtimeScene: gdjs.RuntimeInstanceContainer; _effectsManager: gdjs.EffectsManager; // Lighting layer properties. @@ -31,26 +29,23 @@ namespace gdjs { _clearColor: Array; _rendererEffects: Record = {}; - _renderer: LayerRenderer; + _renderer: gdjs.LayerRenderer; /** * @param layerData The data used to initialize the layer - * @param runtimeScene The scene in which the layer is used + * @param instanceContainer The container in which the layer is used */ - constructor(layerData: LayerData, runtimeScene: gdjs.RuntimeScene) { + constructor( + layerData: LayerData, + instanceContainer: gdjs.RuntimeInstanceContainer + ) { this._name = layerData.name; this._hidden = !layerData.visibility; this._initialEffectsData = layerData.effects || []; - this._cameraX = runtimeScene.getGame().getGameResolutionWidth() / 2; - this._cameraY = runtimeScene.getGame().getGameResolutionHeight() / 2; - this._cachedGameResolutionWidth = runtimeScene - .getGame() - .getGameResolutionWidth(); - this._cachedGameResolutionHeight = runtimeScene - .getGame() - .getGameResolutionHeight(); - this._runtimeScene = runtimeScene; - this._effectsManager = runtimeScene.getGame().getEffectsManager(); + this._runtimeScene = instanceContainer; + this._cameraX = this.getWidth() / 2; + this._cameraY = this.getHeight() / 2; + this._effectsManager = instanceContainer.getGame().getEffectsManager(); this._isLightingLayer = layerData.isLightingLayer; this._followBaseLayerCamera = layerData.followBaseLayerCamera; this._clearColor = [ @@ -59,7 +54,11 @@ namespace gdjs { layerData.ambientLightColorB / 255, 1.0, ]; - this._renderer = new gdjs.LayerRenderer(this, runtimeScene.getRenderer()); + this._renderer = new gdjs.LayerRenderer( + this, + instanceContainer.getRenderer(), + instanceContainer.getGame().getRenderer().getPIXIRenderer() + ); this.show(!this._hidden); for (let i = 0; i < layerData.effects.length; ++i) { this.addEffect(layerData.effects[i]); @@ -90,16 +89,10 @@ namespace gdjs { * Called by the RuntimeScene whenever the game resolution size is changed. * Updates the layer width/height and position. */ - onGameResolutionResized(): void { - const oldGameResolutionWidth = this._cachedGameResolutionWidth; - const oldGameResolutionHeight = this._cachedGameResolutionHeight; - this._cachedGameResolutionWidth = this._runtimeScene - .getGame() - .getGameResolutionWidth(); - this._cachedGameResolutionHeight = this._runtimeScene - .getGame() - .getGameResolutionHeight(); - + onGameResolutionResized( + oldGameResolutionOriginX: float, + oldGameResolutionOriginY: float + ): void { // Adapt position of the camera center as: // * Most cameras following a player/object on the scene will be updating this // in events anyway. @@ -108,24 +101,24 @@ namespace gdjs { // move from its initial position (which is centered in the screen) - and anchor // behavior would behave counterintuitively. this._cameraX += - (this._cachedGameResolutionWidth - oldGameResolutionWidth) / 2; + this._runtimeScene.getViewportOriginX() - oldGameResolutionOriginX; this._cameraY += - (this._cachedGameResolutionHeight - oldGameResolutionHeight) / 2; + this._runtimeScene.getViewportOriginY() - oldGameResolutionOriginY; this._renderer.updatePosition(); } /** - * Returns the scene the layer belongs to - * @returns the scene the layer belongs to + * Returns the scene the layer belongs to directly or indirectly + * @returns the scene the layer belongs to directly or indirectly */ getRuntimeScene(): gdjs.RuntimeScene { - return this._runtimeScene; + return this._runtimeScene.getScene(); } /** * Called at each frame, after events are run and before rendering. */ - updatePreRender(runtimeScene?: gdjs.RuntimeScene): void { + updatePreRender(instanceContainer?: gdjs.RuntimeInstanceContainer): void { if (this._followBaseLayerCamera) { this.followBaseLayer(); } @@ -148,6 +141,7 @@ namespace gdjs { * @return The x position of the camera */ getCameraX(cameraId?: integer): float { + this._forceDimensionUpdate(); return this._cameraX; } @@ -158,6 +152,7 @@ namespace gdjs { * @return The y position of the camera */ getCameraY(cameraId?: integer): float { + this._forceDimensionUpdate(); return this._cameraY; } @@ -168,6 +163,7 @@ namespace gdjs { * @param cameraId The camera number. Currently ignored. */ setCameraX(x: float, cameraId?: integer): void { + this._forceDimensionUpdate(); this._cameraX = x; this._renderer.updatePosition(); } @@ -179,6 +175,7 @@ namespace gdjs { * @param cameraId The camera number. Currently ignored. */ setCameraY(y: float, cameraId?: integer): void { + this._forceDimensionUpdate(); this._cameraY = y; this._renderer.updatePosition(); } @@ -191,7 +188,7 @@ namespace gdjs { * @return The width of the camera */ getCameraWidth(cameraId?: integer): float { - return (+this._cachedGameResolutionWidth * 1) / this._zoomFactor; + return this.getWidth() / this._zoomFactor; } /** @@ -202,7 +199,7 @@ namespace gdjs { * @return The height of the camera */ getCameraHeight(cameraId?: integer): float { - return (+this._cachedGameResolutionHeight * 1) / this._zoomFactor; + return this.getHeight() / this._zoomFactor; } /** @@ -268,15 +265,51 @@ namespace gdjs { /** * Convert a point from the canvas coordinates (for example, - * the mouse position) to the scene coordinates. + * the mouse position) to the container coordinates. * * @param x The x position, in canvas coordinates. * @param y The y position, in canvas coordinates. * @param cameraId The camera number. Currently ignored. + * @param result The point instance that is used to return the result. */ - convertCoords(x: float, y: float, cameraId?: integer): FloatPoint { - x -= this._cachedGameResolutionWidth / 2; - y -= this._cachedGameResolutionHeight / 2; + convertCoords( + x: float, + y: float, + cameraId: integer = 0, + result: FloatPoint + ): FloatPoint { + // The result parameter used to be optional. + let position = result || [0, 0]; + // TODO EBO use an AffineTransformation to avoid chained calls. + position = this._runtimeScene.convertCoords(x, y, position); + return this.applyLayerInverseTransformation( + position[0], + position[1], + cameraId, + position + ); + } + + /** + * Return an array containing the coordinates of the point passed as parameter + * in layer local coordinates (as opposed to the parent coordinates). + * + * All transformations (scale, rotation) are supported. + * + * @param x The X position of the point, in parent coordinates. + * @param y The Y position of the point, in parent coordinates. + * @param result Array that will be updated with the result + * @param result The point instance that is used to return the result. + * (x and y position of the point in layer coordinates). + */ + applyLayerInverseTransformation( + x: float, + y: float, + cameraId: integer, + result: FloatPoint + ): FloatPoint { + x -= this._runtimeScene.getViewportOriginX(); + y -= this._runtimeScene.getViewportOriginY(); x /= Math.abs(this._zoomFactor); y /= Math.abs(this._zoomFactor); @@ -287,18 +320,54 @@ namespace gdjs { const sinValue = Math.sin(angleInRadians); x = cosValue * x - sinValue * y; y = sinValue * tmp + cosValue * y; - return [x + this.getCameraX(cameraId), y + this.getCameraY(cameraId)]; + result[0] = x + this.getCameraX(cameraId); + result[1] = y + this.getCameraY(cameraId); + + return result; } /** - * Convert a point from the scene coordinates (for example, + * Convert a point from the container coordinates (for example, * an object position) to the canvas coordinates. * - * @param x The x position, in scene coordinates. - * @param y The y position, in scene coordinates. + * @param x The x position, in container coordinates. + * @param y The y position, in container coordinates. * @param cameraId The camera number. Currently ignored. + * @param result The point instance that is used to return the result. */ - convertInverseCoords(x: float, y: float, cameraId?: integer): FloatPoint { + convertInverseCoords( + x: float, + y: float, + cameraId: integer = 0, + result: FloatPoint + ): FloatPoint { + let position = result || [0, 0]; + // TODO EBO use an AffineTransformation to avoid chained calls. + this.applyLayerTransformation(x, y, cameraId, position); + return this._runtimeScene.convertInverseCoords( + position[0], + position[1], + position + ); + } + + /** + * Return an array containing the coordinates of the point passed as parameter + * in parent coordinate coordinates (as opposed to the layer local coordinates). + * + * All transformations (scale, rotation) are supported. + * + * @param x The X position of the point, in layer coordinates. + * @param y The Y position of the point, in layer coordinates. + * @param result Array that will be updated with the result + * (x and y position of the point in parent coordinates). + */ + applyLayerTransformation( + x: float, + y: float, + cameraId: integer, + result: FloatPoint + ): FloatPoint { x -= this.getCameraX(cameraId); y -= this.getCameraY(cameraId); @@ -311,18 +380,31 @@ namespace gdjs { y = sinValue * tmp + cosValue * y; x *= Math.abs(this._zoomFactor); y *= Math.abs(this._zoomFactor); - return [ - x + this._cachedGameResolutionWidth / 2, - y + this._cachedGameResolutionHeight / 2, - ]; + x += this._runtimeScene.getViewportOriginX(); + y += this._runtimeScene.getViewportOriginY(); + + result[0] = x; + result[1] = y; + return result; } getWidth(): float { - return this._cachedGameResolutionWidth; + return this._runtimeScene.getViewportWidth(); } getHeight(): float { - return this._cachedGameResolutionHeight; + return this._runtimeScene.getViewportHeight(); + } + + /** + * This ensure that the viewport dimensions are up to date. + * + * It's needed because custom objects dimensions are only updated on + * demand for efficiency reasons. + */ + private _forceDimensionUpdate(): void { + // This will update dimensions. + this._runtimeScene.getViewportWidth(); } /** @@ -464,10 +546,12 @@ namespace gdjs { /** * Return the time elapsed since the last frame, * in milliseconds, for objects on the layer. + * + * @param instanceContainer The instance container the layer belongs to (deprecated - can be omitted). */ - getElapsedTime(runtimeScene?: RuntimeScene): float { - runtimeScene = runtimeScene || this._runtimeScene; - return runtimeScene.getTimeManager().getElapsedTime() * this._timeScale; + getElapsedTime(instanceContainer?: gdjs.RuntimeInstanceContainer): float { + const container = instanceContainer || this._runtimeScene; + return container.getElapsedTime() * this._timeScale; } /** diff --git a/GDJS/Runtime/pixi-renderers/CustomObjectPixiRenderer.ts b/GDJS/Runtime/pixi-renderers/CustomObjectPixiRenderer.ts new file mode 100644 index 0000000000..bec1e2fdea --- /dev/null +++ b/GDJS/Runtime/pixi-renderers/CustomObjectPixiRenderer.ts @@ -0,0 +1,152 @@ +namespace gdjs { + import PIXI = GlobalPIXIModule.PIXI; + + /** + * The renderer for a {@link gdjs.CustomRuntimeObject} using Pixi.js. + */ + export class CustomObjectPixiRenderer + implements gdjs.RuntimeInstanceContainerPixiRenderer { + _object: gdjs.CustomRuntimeObject; + _instanceContainer: gdjs.CustomRuntimeObjectInstanceContainer; + _pixiContainer: PIXI.Container; + _isContainerDirty: boolean = true; + _debugDraw: PIXI.Graphics | null = null; + _debugDrawContainer: PIXI.Container | null = null; + _debugDrawRenderedObjectsPoints: Record< + number, + { + wasRendered: boolean; + points: Record; + } + >; + + constructor( + object: gdjs.CustomRuntimeObject, + instanceContainer: gdjs.CustomRuntimeObjectInstanceContainer, + parent: gdjs.RuntimeInstanceContainer + ) { + this._object = object; + this._instanceContainer = instanceContainer; + this._pixiContainer = new PIXI.Container(); + this._debugDrawRenderedObjectsPoints = {}; + + // Contains the layers of the scene (and, optionally, debug PIXI objects). + this._pixiContainer.sortableChildren = true; + this._debugDraw = null; + + const layer = parent.getLayer(''); + if (layer) { + layer + .getRenderer() + .addRendererObject(this._pixiContainer, object.getZOrder()); + } + } + + reinitialize( + object: gdjs.CustomRuntimeObject, + parent: gdjs.RuntimeInstanceContainer + ) { + this._object = object; + this._isContainerDirty = true; + const layer = parent.getLayer(''); + if (layer) { + layer + .getRenderer() + .addRendererObject(this._pixiContainer, object.getZOrder()); + } + } + + getRendererObject() { + return this._pixiContainer; + } + + /** + * Update the internal PIXI.Container position, angle... + */ + _updatePIXIContainer() { + this._pixiContainer.pivot.x = this._object.getUnscaledCenterX(); + this._pixiContainer.pivot.y = this._object.getUnscaledCenterY(); + this._pixiContainer.position.x = + this._object.getDrawableX() + + this._pixiContainer.pivot.x * Math.abs(this._object._scaleX); + this._pixiContainer.position.y = + this._object.getDrawableY() + + this._pixiContainer.pivot.y * Math.abs(this._object._scaleY); + this._pixiContainer.rotation = gdjs.toRad(this._object.angle); + this._pixiContainer.scale.x = this._object._scaleX; + this._pixiContainer.scale.y = this._object._scaleY; + this._pixiContainer.visible = !this._object.hidden; + this._pixiContainer.alpha = this._object.opacity / 255; + + this._isContainerDirty = false; + } + + /** + * Call this to make sure the sprite is ready to be rendered. + */ + ensureUpToDate() { + if (this._isContainerDirty) { + this._updatePIXIContainer(); + } + } + + update(): void { + this._isContainerDirty = true; + } + + updateX(): void { + this._pixiContainer.position.x = + this._object.x + + this._pixiContainer.pivot.x * Math.abs(this._object._scaleX); + } + + updateY(): void { + this._pixiContainer.position.y = + this._object.y + + this._pixiContainer.pivot.y * Math.abs(this._object._scaleY); + } + + updateAngle(): void { + this._pixiContainer.rotation = gdjs.toRad(this._object.angle); + } + + updateOpacity(): void { + this._pixiContainer.alpha = this._object.opacity / 255; + } + + updateVisibility(): void { + this._pixiContainer.visible = !this._object.hidden; + } + + getPIXIContainer() { + return this._pixiContainer; + } + + getPIXIRenderer() { + return null; + } + + setLayerIndex(layer: gdjs.Layer, index: float): void { + const layerPixiRenderer: gdjs.LayerPixiRenderer = layer.getRenderer(); + let layerPixiObject: + | PIXI.Container + | PIXI.Sprite + | null = layerPixiRenderer.getRendererObject(); + if (layer.isLightingLayer()) { + layerPixiObject = layerPixiRenderer.getLightingSprite(); + } + if (!layerPixiObject) { + return; + } + if (this._pixiContainer.children.indexOf(layerPixiObject) === index) { + return; + } + this._pixiContainer.removeChild(layerPixiObject); + this._pixiContainer.addChildAt(layerPixiObject, index); + } + } + + // Register the class to let the engine use it. + export type CustomObjectRenderer = gdjs.CustomObjectPixiRenderer; + export const CustomObjectRenderer = gdjs.CustomObjectPixiRenderer; +} diff --git a/GDJS/Runtime/pixi-renderers/DebuggerPixiRenderer.ts b/GDJS/Runtime/pixi-renderers/DebuggerPixiRenderer.ts new file mode 100644 index 0000000000..5cf3fb038f --- /dev/null +++ b/GDJS/Runtime/pixi-renderers/DebuggerPixiRenderer.ts @@ -0,0 +1,329 @@ +namespace gdjs { + import PIXI = GlobalPIXIModule.PIXI; + + /** + * A renderer for debug instances location of a container using Pixi.js. + * + * @see gdjs.CustomObjectPixiRenderer + */ + export class DebuggerPixiRenderer { + _instanceContainer: gdjs.RuntimeInstanceContainer; + _debugDraw: PIXI.Graphics | null = null; + _debugDrawContainer: PIXI.Container | null = null; + _debugDrawRenderedObjectsPoints: Record< + number, + { + wasRendered: boolean; + points: Record; + } + >; + + constructor(instanceContainer: gdjs.RuntimeInstanceContainer) { + this._instanceContainer = instanceContainer; + this._debugDrawRenderedObjectsPoints = {}; + this._debugDraw = null; + } + + getRendererObject() { + return this._debugDrawContainer; + } + + /** + * Render graphics for debugging purpose. Activate this in `gdjs.RuntimeScene`, + * in the `renderAndStep` method. + * @see gdjs.RuntimeInstanceContainer#enableDebugDraw + */ + renderDebugDraw( + instances: gdjs.RuntimeObject[], + showHiddenInstances: boolean, + showPointsNames: boolean, + showCustomPoints: boolean + ) { + const pixiContainer = this._instanceContainer + .getRenderer() + .getRendererObject(); + if (!this._debugDraw || !this._debugDrawContainer) { + this._debugDrawContainer = new PIXI.Container(); + this._debugDraw = new PIXI.Graphics(); + + // Add on top of all layers: + this._debugDrawContainer.addChild(this._debugDraw); + pixiContainer.addChild(this._debugDrawContainer); + } + const debugDraw = this._debugDraw; + + // Reset the boolean "wasRendered" of all points of objects to false: + for (let id in this._debugDrawRenderedObjectsPoints) { + this._debugDrawRenderedObjectsPoints[id].wasRendered = false; + } + + const renderObjectPoint = ( + points: Record, + name: string, + fillColor: integer, + x: float, + y: float + ) => { + debugDraw.line.color = fillColor; + debugDraw.fill.color = fillColor; + debugDraw.drawCircle(x, y, 3); + + if (showPointsNames) { + if (!points[name]) { + points[name] = new PIXI.Text(name, { + fill: fillColor, + fontSize: 12, + }); + + this._debugDrawContainer!.addChild(points[name]); + } + + points[name].position.set(x, y); + } + }; + + debugDraw.clear(); + debugDraw.beginFill(); + debugDraw.alpha = 0.8; + debugDraw.lineStyle(2, 0x0000ff, 1); + + // Draw AABB + const workingPoint: FloatPoint = [0, 0]; + for (let i = 0; i < instances.length; i++) { + const object = instances[i]; + const layer = this._instanceContainer.getLayer(object.getLayer()); + + if ( + (!object.isVisible() || !layer.isVisible()) && + !showHiddenInstances + ) { + continue; + } + + const rendererObject = object.getRendererObject(); + if (!rendererObject) { + continue; + } + const aabb = object.getAABB(); + debugDraw.fill.alpha = 0.2; + debugDraw.line.color = 0x778ee8; + debugDraw.fill.color = 0x778ee8; + + const polygon: float[] = []; + polygon.push.apply( + polygon, + layer.applyLayerTransformation( + aabb.min[0], + aabb.min[1], + 0, + workingPoint + ) + ); + polygon.push.apply( + polygon, + layer.applyLayerTransformation( + aabb.max[0], + aabb.min[1], + 0, + workingPoint + ) + ); + polygon.push.apply( + polygon, + layer.applyLayerTransformation( + aabb.max[0], + aabb.max[1], + 0, + workingPoint + ) + ); + polygon.push.apply( + polygon, + layer.applyLayerTransformation( + aabb.min[0], + aabb.max[1], + 0, + workingPoint + ) + ); + + debugDraw.drawPolygon(polygon); + } + + // Draw hitboxes and points + for (let i = 0; i < instances.length; i++) { + const object = instances[i]; + const layer = this._instanceContainer.getLayer(object.getLayer()); + + if ( + (!object.isVisible() || !layer.isVisible()) && + !showHiddenInstances + ) { + continue; + } + + const rendererObject = object.getRendererObject(); + if (!rendererObject) { + continue; + } + + // Create the structure to store the points in memory + const id = object.id; + if (!this._debugDrawRenderedObjectsPoints[id]) { + this._debugDrawRenderedObjectsPoints[id] = { + wasRendered: true, + points: {}, + }; + } + const renderedObjectPoints = this._debugDrawRenderedObjectsPoints[id]; + renderedObjectPoints.wasRendered = true; + + // Draw hitboxes (sub-optimal performance) + const hitboxes = object.getHitBoxes(); + for (let j = 0; j < hitboxes.length; j++) { + // Note that this conversion is sub-optimal, but we don't care + // as this is for debug draw. + const polygon: float[] = []; + hitboxes[j].vertices.forEach((point) => { + point = layer.applyLayerTransformation( + point[0], + point[1], + 0, + workingPoint + ); + + polygon.push(point[0]); + polygon.push(point[1]); + }); + debugDraw.fill.alpha = 0; + debugDraw.line.alpha = 0.5; + debugDraw.line.color = 0xff0000; + debugDraw.drawPolygon(polygon); + } + + // Draw points + debugDraw.fill.alpha = 0.3; + + // Draw Center point + const centerPoint = layer.applyLayerTransformation( + object.getCenterXInScene(), + object.getCenterYInScene(), + 0, + workingPoint + ); + + renderObjectPoint( + renderedObjectPoints.points, + 'Center', + 0xffff00, + centerPoint[0], + centerPoint[1] + ); + + // Draw position point + const positionPoint = layer.applyLayerTransformation( + object.getX(), + object.getY(), + 0, + workingPoint + ); + + renderObjectPoint( + renderedObjectPoints.points, + 'Position', + 0xff0000, + positionPoint[0], + positionPoint[1] + ); + + // Draw Origin point + if (object instanceof gdjs.SpriteRuntimeObject) { + let originPoint = object.getPointPosition('origin'); + // When there is neither rotation nor flipping, + // the origin point is over the position point. + if ( + Math.abs(originPoint[0] - positionPoint[0]) >= 1 || + Math.abs(originPoint[1] - positionPoint[1]) >= 1 + ) { + originPoint = layer.applyLayerTransformation( + originPoint[0], + originPoint[1], + 0, + workingPoint + ); + + renderObjectPoint( + renderedObjectPoints.points, + 'Origin', + 0xff0000, + originPoint[0], + originPoint[1] + ); + } + } + + // Draw custom point + if (showCustomPoints && object instanceof gdjs.SpriteRuntimeObject) { + if (!object._animationFrame) continue; + + for (const customPointName in object._animationFrame.points.items) { + let customPoint = object.getPointPosition(customPointName); + + customPoint = layer.applyLayerTransformation( + customPoint[0], + customPoint[1], + 0, + workingPoint + ); + + renderObjectPoint( + renderedObjectPoints.points, + customPointName, + 0x0000ff, + customPoint[0], + customPoint[1] + ); + } + } + } + + // Clean any point text from an object that is not rendered. + for (const objectID in this._debugDrawRenderedObjectsPoints) { + const renderedObjectPoints = this._debugDrawRenderedObjectsPoints[ + objectID + ]; + if (renderedObjectPoints.wasRendered) continue; + + const points = renderedObjectPoints.points; + for (const name in points) { + this._debugDrawContainer.removeChild(points[name]); + } + } + + debugDraw.endFill(); + } + + clearDebugDraw(): void { + if (this._debugDraw) { + this._debugDraw.clear(); + } + + if (this._debugDrawContainer) { + this._debugDrawContainer.destroy({ + children: true, + }); + const pixiContainer: PIXI.Container = this._instanceContainer + .getRenderer() + .getRendererObject(); + pixiContainer.removeChild(this._debugDrawContainer); + } + this._debugDraw = null; + this._debugDrawContainer = null; + this._debugDrawRenderedObjectsPoints = {}; + } + } + + // Register the class to let the engine use it. + export type DebuggerRenderer = gdjs.DebuggerPixiRenderer; + export const DebuggerRenderer = gdjs.DebuggerPixiRenderer; +} diff --git a/GDJS/Runtime/pixi-renderers/RuntimeInstanceContainerPixiRenderer.ts b/GDJS/Runtime/pixi-renderers/RuntimeInstanceContainerPixiRenderer.ts new file mode 100644 index 0000000000..6b8cd25fac --- /dev/null +++ b/GDJS/Runtime/pixi-renderers/RuntimeInstanceContainerPixiRenderer.ts @@ -0,0 +1,30 @@ +/* + * GDevelop JS Platform + * Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved. + * This project is released under the MIT License. + */ +namespace gdjs { + import PIXI = GlobalPIXIModule.PIXI; + + /** + * A render for instance container. + * + * @see gdjs.RuntimeInstanceContainer + */ + export interface RuntimeInstanceContainerPixiRenderer { + /** + * Change the position of a layer. + * + * @param layer The layer to reorder + * @param index The new position in the list of layers + * + * @see gdjs.RuntimeInstanceContainer.setLayerIndex + */ + setLayerIndex(layer: gdjs.Layer, index: integer): void; + + getRendererObject(): PIXI.Container; + } + + // Register the class to let the engine use it. + export type RuntimeInstanceContainerRenderer = gdjs.RuntimeInstanceContainerPixiRenderer; +} diff --git a/GDJS/Runtime/pixi-renderers/layer-pixi-renderer.ts b/GDJS/Runtime/pixi-renderers/layer-pixi-renderer.ts index f468c3fe1a..3d9c0d0743 100644 --- a/GDJS/Runtime/pixi-renderers/layer-pixi-renderer.ts +++ b/GDJS/Runtime/pixi-renderers/layer-pixi-renderer.ts @@ -16,7 +16,7 @@ namespace gdjs { _layer: gdjs.Layer; _renderTexture: PIXI.RenderTexture | null = null; _lightingSprite: PIXI.Sprite | null = null; - _runtimeSceneRenderer: any; + _runtimeSceneRenderer: gdjs.RuntimeInstanceContainerRenderer; _pixiRenderer: PIXI.Renderer | null; // Width and height are tracked when a render texture is used. @@ -27,19 +27,22 @@ namespace gdjs { /** * @param layer The layer - * @param runtimeSceneRenderer The scene renderer + * @param runtimeInstanceContainerRenderer The scene renderer */ constructor( layer: gdjs.Layer, - runtimeSceneRenderer: gdjs.RuntimeScenePixiRenderer + runtimeInstanceContainerRenderer: gdjs.RuntimeInstanceContainerRenderer, + pixiRenderer: PIXI.Renderer | null ) { this._pixiContainer = new PIXI.Container(); this._layer = layer; - this._runtimeSceneRenderer = runtimeSceneRenderer; - this._pixiRenderer = runtimeSceneRenderer.getPIXIRenderer(); + this._runtimeSceneRenderer = runtimeInstanceContainerRenderer; + this._pixiRenderer = pixiRenderer; this._isLightingLayer = layer.isLightingLayer(); this._clearColor = layer.getClearColor(); - runtimeSceneRenderer.getPIXIContainer().addChild(this._pixiContainer); + runtimeInstanceContainerRenderer + .getRendererObject() + .addChild(this._pixiContainer); this._pixiContainer.filters = []; if (this._isLightingLayer) { this._replaceContainerWithSprite(); @@ -244,7 +247,7 @@ namespace gdjs { } this._lightingSprite = new PIXI.Sprite(this._renderTexture); this._lightingSprite.blendMode = PIXI.BLEND_MODES.MULTIPLY; - const sceneContainer = this._runtimeSceneRenderer.getPIXIContainer(); + const sceneContainer = this._runtimeSceneRenderer.getRendererObject(); const index = sceneContainer.getChildIndex(this._pixiContainer); sceneContainer.addChildAt(this._lightingSprite, index); sceneContainer.removeChild(this._pixiContainer); diff --git a/GDJS/Runtime/pixi-renderers/pixi-effects-manager.ts b/GDJS/Runtime/pixi-renderers/pixi-effects-manager.ts index f84a0087dd..c7b3eda465 100644 --- a/GDJS/Runtime/pixi-renderers/pixi-effects-manager.ts +++ b/GDJS/Runtime/pixi-renderers/pixi-effects-manager.ts @@ -9,8 +9,10 @@ namespace gdjs { type RendererEffects = Record; export interface EffectsTarget { - getRuntimeScene: () => RuntimeScene; - getElapsedTime: (runtimeScene?: RuntimeScene) => number; + getRuntimeScene: () => gdjs.RuntimeInstanceContainer; + getElapsedTime: ( + instanceContainer?: gdjs.RuntimeInstanceContainer + ) => number; getHeight: () => number; getWidth: () => number; isLightingLayer?: () => boolean; diff --git a/GDJS/Runtime/pixi-renderers/runtimegame-pixi-renderer.ts b/GDJS/Runtime/pixi-renderers/runtimegame-pixi-renderer.ts index cc28305396..e2c650298d 100644 --- a/GDJS/Runtime/pixi-renderers/runtimegame-pixi-renderer.ts +++ b/GDJS/Runtime/pixi-renderers/runtimegame-pixi-renderer.ts @@ -409,18 +409,24 @@ namespace gdjs { * Convert a point from the canvas coordinates to the dom element container coordinates. * * @param canvasCoords The point in the canvas coordinates. + * @param result The point to return. * @returns The point in the dom element container coordinates. */ convertCanvasToDomElementContainerCoords( - canvasCoords: FloatPoint + canvasCoords: FloatPoint, + result: FloatPoint ): FloatPoint { - const pageCoords: FloatPoint = [canvasCoords[0], canvasCoords[1]]; + const pageCoords = result || [0, 0]; // Handle the fact that the game is stretched to fill the canvas. - pageCoords[0] /= - this._game.getGameResolutionWidth() / (this._canvasWidth || 1); - pageCoords[1] /= - this._game.getGameResolutionHeight() / (this._canvasHeight || 1); + pageCoords[0] = + canvasCoords[0] / + this._game.getGameResolutionWidth() / + (this._canvasWidth || 1); + pageCoords[1] = + canvasCoords[1] / + this._game.getGameResolutionHeight() / + (this._canvasHeight || 1); return pageCoords; } diff --git a/GDJS/Runtime/pixi-renderers/runtimescene-pixi-renderer.ts b/GDJS/Runtime/pixi-renderers/runtimescene-pixi-renderer.ts index d7f570ab4f..2996f97f21 100644 --- a/GDJS/Runtime/pixi-renderers/runtimescene-pixi-renderer.ts +++ b/GDJS/Runtime/pixi-renderers/runtimescene-pixi-renderer.ts @@ -4,21 +4,13 @@ namespace gdjs { /** * The renderer for a gdjs.RuntimeScene using Pixi.js. */ - export class RuntimeScenePixiRenderer { + export class RuntimeScenePixiRenderer + implements gdjs.RuntimeInstanceContainerPixiRenderer { _pixiRenderer: PIXI.Renderer | null; _runtimeScene: gdjs.RuntimeScene; _pixiContainer: PIXI.Container; - _debugDraw: PIXI.Graphics | null = null; - _debugDrawContainer: PIXI.Container | null = null; _profilerText: PIXI.Text | null = null; _showCursorAtNextRender: boolean = false; - _debugDrawRenderedObjectsPoints: Record< - number, - { - wasRendered: boolean; - points: Record; - } - >; constructor( runtimeScene: gdjs.RuntimeScene, @@ -29,11 +21,9 @@ namespace gdjs { : null; this._runtimeScene = runtimeScene; this._pixiContainer = new PIXI.Container(); - this._debugDrawRenderedObjectsPoints = {}; // Contains the layers of the scene (and, optionally, debug PIXI objects). this._pixiContainer.sortableChildren = true; - this._debugDraw = null; } onGameResolutionResized() { @@ -90,259 +80,6 @@ namespace gdjs { this._profilerText.text = outputs.join('\n'); } - /** - * Render graphics for debugging purpose. Activate this in `gdjs.RuntimeScene`, - * in the `renderAndStep` method. - */ - renderDebugDraw( - instances: gdjs.RuntimeObject[], - showHiddenInstances: boolean, - showPointsNames: boolean, - showCustomPoints: boolean - ) { - if (!this._debugDraw || !this._debugDrawContainer) { - this._debugDrawContainer = new PIXI.Container(); - this._debugDraw = new PIXI.Graphics(); - - // Add on top of all layers: - this._debugDrawContainer.addChild(this._debugDraw); - this._pixiContainer.addChild(this._debugDrawContainer); - } - const debugDraw = this._debugDraw; - - // Reset the boolean "wasRendered" of all points of objects to false: - for (let id in this._debugDrawRenderedObjectsPoints) { - this._debugDrawRenderedObjectsPoints[id].wasRendered = false; - } - - const renderObjectPoint = ( - points: Record, - name: string, - fillColor: integer, - x: float, - y: float - ) => { - debugDraw.line.color = fillColor; - debugDraw.fill.color = fillColor; - debugDraw.drawCircle(x, y, 3); - - if (showPointsNames) { - if (!points[name]) { - points[name] = new PIXI.Text(name, { - fill: fillColor, - fontSize: 12, - }); - - this._debugDrawContainer!.addChild(points[name]); - } - - points[name].position.set(x, y); - } - }; - - debugDraw.clear(); - debugDraw.beginFill(); - debugDraw.alpha = 0.8; - debugDraw.lineStyle(2, 0x0000ff, 1); - - // Draw AABB - for (let i = 0; i < instances.length; i++) { - const object = instances[i]; - const layer = this._runtimeScene.getLayer(object.getLayer()); - - if ( - (!object.isVisible() || !layer.isVisible()) && - !showHiddenInstances - ) { - continue; - } - - const rendererObject = object.getRendererObject(); - if (!rendererObject) { - continue; - } - const aabb = object.getAABB(); - debugDraw.fill.alpha = 0.2; - debugDraw.line.color = 0x778ee8; - debugDraw.fill.color = 0x778ee8; - - const polygon: float[] = []; - polygon.push.apply( - polygon, - layer.convertInverseCoords(aabb.min[0], aabb.min[1]) - ); - polygon.push.apply( - polygon, - layer.convertInverseCoords(aabb.max[0], aabb.min[1]) - ); - polygon.push.apply( - polygon, - layer.convertInverseCoords(aabb.max[0], aabb.max[1]) - ); - polygon.push.apply( - polygon, - layer.convertInverseCoords(aabb.min[0], aabb.max[1]) - ); - - debugDraw.drawPolygon(polygon); - } - - // Draw hitboxes and points - for (let i = 0; i < instances.length; i++) { - const object = instances[i]; - const layer = this._runtimeScene.getLayer(object.getLayer()); - - if ( - (!object.isVisible() || !layer.isVisible()) && - !showHiddenInstances - ) { - continue; - } - - const rendererObject = object.getRendererObject(); - if (!rendererObject) { - continue; - } - - // Create the structure to store the points in memory - const id = object.id; - if (!this._debugDrawRenderedObjectsPoints[id]) { - this._debugDrawRenderedObjectsPoints[id] = { - wasRendered: true, - points: {}, - }; - } - const renderedObjectPoints = this._debugDrawRenderedObjectsPoints[id]; - renderedObjectPoints.wasRendered = true; - - // Draw hitboxes (sub-optimal performance) - const hitboxes = object.getHitBoxes(); - for (let j = 0; j < hitboxes.length; j++) { - // Note that this conversion is sub-optimal, but we don't care - // as this is for debug draw. - const polygon: float[] = []; - hitboxes[j].vertices.forEach((point) => { - point = layer.convertInverseCoords(point[0], point[1]); - - polygon.push(point[0]); - polygon.push(point[1]); - }); - debugDraw.fill.alpha = 0; - debugDraw.line.alpha = 0.5; - debugDraw.line.color = 0xff0000; - debugDraw.drawPolygon(polygon); - } - - // Draw points - debugDraw.fill.alpha = 0.3; - - // Draw Center point - const centerPoint = layer.convertInverseCoords( - object.getCenterXInScene(), - object.getCenterYInScene() - ); - - renderObjectPoint( - renderedObjectPoints.points, - 'Center', - 0xffff00, - centerPoint[0], - centerPoint[1] - ); - - // Draw position point - const positionPoint = layer.convertInverseCoords( - object.getX(), - object.getY() - ); - - renderObjectPoint( - renderedObjectPoints.points, - 'Position', - 0xff0000, - positionPoint[0], - positionPoint[1] - ); - - // Draw Origin point - if (object instanceof gdjs.SpriteRuntimeObject) { - let originPoint = object.getPointPosition('origin'); - // When there is neither rotation nor flipping, - // the origin point is over the position point. - if ( - Math.abs(originPoint[0] - positionPoint[0]) >= 1 || - Math.abs(originPoint[1] - positionPoint[1]) >= 1 - ) { - originPoint = layer.convertInverseCoords( - originPoint[0], - originPoint[1] - ); - - renderObjectPoint( - renderedObjectPoints.points, - 'Origin', - 0xff0000, - originPoint[0], - originPoint[1] - ); - } - } - - // Draw custom point - if (showCustomPoints && object instanceof gdjs.SpriteRuntimeObject) { - if (!object._animationFrame) continue; - - for (const customPointName in object._animationFrame.points.items) { - let customPoint = object.getPointPosition(customPointName); - - customPoint = layer.convertInverseCoords( - customPoint[0], - customPoint[1] - ); - - renderObjectPoint( - renderedObjectPoints.points, - customPointName, - 0x0000ff, - customPoint[0], - customPoint[1] - ); - } - } - } - - // Clean any point text from an object that is not rendered. - for (const objectID in this._debugDrawRenderedObjectsPoints) { - const renderedObjectPoints = this._debugDrawRenderedObjectsPoints[ - objectID - ]; - if (renderedObjectPoints.wasRendered) continue; - - const points = renderedObjectPoints.points; - for (const name in points) { - this._debugDrawContainer.removeChild(points[name]); - } - } - - debugDraw.endFill(); - } - - clearDebugDraw(): void { - if (this._debugDraw) { - this._debugDraw.clear(); - } - - if (this._debugDrawContainer) { - this._debugDrawContainer.destroy({ - children: true, - }); - this._pixiContainer.removeChild(this._debugDrawContainer); - } - this._debugDraw = null; - this._debugDrawContainer = null; - this._debugDrawRenderedObjectsPoints = {}; - } - hideCursor(): void { this._showCursorAtNextRender = false; if (!this._pixiRenderer) { @@ -359,6 +96,10 @@ namespace gdjs { return this._pixiContainer; } + getRendererObject() { + return this._pixiContainer; + } + getPIXIRenderer() { return this._pixiRenderer; } @@ -383,7 +124,7 @@ namespace gdjs { } } - //Register the class to let the engine use it. + // Register the class to let the engine use it. export type RuntimeSceneRenderer = gdjs.RuntimeScenePixiRenderer; export const RuntimeSceneRenderer = gdjs.RuntimeScenePixiRenderer; } diff --git a/GDJS/Runtime/pixi-renderers/spriteruntimeobject-pixi-renderer.ts b/GDJS/Runtime/pixi-renderers/spriteruntimeobject-pixi-renderer.ts index 99adb3ee32..d618a8170c 100644 --- a/GDJS/Runtime/pixi-renderers/spriteruntimeobject-pixi-renderer.ts +++ b/GDJS/Runtime/pixi-renderers/spriteruntimeobject-pixi-renderer.ts @@ -14,19 +14,17 @@ namespace gdjs { /** * @param runtimeObject The object - * @param runtimeScene The scene + * @param instanceContainer The scene */ constructor( runtimeObject: gdjs.SpriteRuntimeObject, - runtimeScene: gdjs.RuntimeScene + instanceContainer: gdjs.RuntimeInstanceContainer ) { this._object = runtimeObject; - if (this._sprite === undefined) { - this._sprite = new PIXI.Sprite( - runtimeScene.getGame().getImageManager().getInvalidPIXITexture() - ); - } - const layer = runtimeScene.getLayer(''); + this._sprite = new PIXI.Sprite( + instanceContainer.getGame().getImageManager().getInvalidPIXITexture() + ); + const layer = instanceContainer.getLayer(''); if (layer) { layer .getRenderer() @@ -34,11 +32,14 @@ namespace gdjs { } } - reinitialize(runtimeObject, runtimeScene) { + reinitialize( + runtimeObject: gdjs.SpriteRuntimeObject, + instanceContainer: gdjs.RuntimeInstanceContainer + ) { this._object = runtimeObject; this._spriteDirty = true; this._textureDirty = true; - const layer = runtimeScene.getLayer(''); + const layer = instanceContainer.getLayer(''); if (layer) { layer .getRenderer() diff --git a/GDJS/Runtime/runtimebehavior.ts b/GDJS/Runtime/runtimebehavior.ts index 81077e9701..663231f9a1 100644 --- a/GDJS/Runtime/runtimebehavior.ts +++ b/GDJS/Runtime/runtimebehavior.ts @@ -41,12 +41,12 @@ namespace gdjs { _activated: boolean = true; /** - * @param runtimeScene The scene owning the object of the behavior + * @param instanceContainer The container owning the object of the behavior * @param behaviorData The properties used to setup the behavior * @param owner The object owning the behavior */ constructor( - runtimeScene: gdjs.RuntimeScene, + instanceContainer: gdjs.RuntimeInstanceContainer, behaviorData: BehaviorData, public owner: gdjs.RuntimeObject ) { @@ -92,15 +92,15 @@ namespace gdjs { /** * Called at each frame before events. Call doStepPreEvents.
* Behaviors writers: Please do not redefine this method. Redefine doStepPreEvents instead. - * @param runtimeScene The runtimeScene owning the object + * @param instanceContainer The instanceContainer owning the object */ - stepPreEvents(runtimeScene: gdjs.RuntimeScene): void { + stepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer): void { if (this._activated) { - const profiler = runtimeScene.getProfiler(); + const profiler = instanceContainer.getScene().getProfiler(); if (profiler) { profiler.begin(this.name); } - this.doStepPreEvents(runtimeScene); + this.doStepPreEvents(instanceContainer); if (profiler) { profiler.end(this.name); } @@ -110,15 +110,15 @@ namespace gdjs { /** * Called at each frame after events. Call doStepPostEvents.
* Behaviors writers: Please do not redefine this method. Redefine doStepPreEvents instead. - * @param runtimeScene The runtimeScene owning the object + * @param instanceContainer The instanceContainer owning the object */ - stepPostEvents(runtimeScene: gdjs.RuntimeScene): void { + stepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer): void { if (this._activated) { - const profiler = runtimeScene.getProfiler(); + const profiler = instanceContainer.getScene().getProfiler(); if (profiler) { profiler.begin(this.name); } - this.doStepPostEvents(runtimeScene); + this.doStepPostEvents(instanceContainer); if (profiler) { profiler.end(this.name); } @@ -171,15 +171,15 @@ namespace gdjs { /** * This method is called each tick before events are done. - * @param runtimeScene The runtimeScene owning the object + * @param instanceContainer The instanceContainer owning the object */ - doStepPreEvents(runtimeScene: gdjs.RuntimeScene): void {} + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer): void {} /** * This method is called each tick after events are done. - * @param runtimeScene The runtimeScene owning the object + * @param instanceContainer The instanceContainer owning the object */ - doStepPostEvents(runtimeScene: gdjs.RuntimeScene): void {} + doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer): void {} /** * This method is called when the owner of the behavior diff --git a/GDJS/Runtime/runtimegame.ts b/GDJS/Runtime/runtimegame.ts index 40c7952b51..134444ff58 100644 --- a/GDJS/Runtime/runtimegame.ts +++ b/GDJS/Runtime/runtimegame.ts @@ -55,6 +55,7 @@ namespace gdjs { export class RuntimeGame { _variables: VariablesContainer; _data: ProjectData; + _eventsBasedObjectDatas: Map; _imageManager: ImageManager; _soundManager: SoundManager; _fontManager: FontManager; @@ -142,6 +143,18 @@ namespace gdjs { this._sessionId = null; this._playerId = null; + this._eventsBasedObjectDatas = new Map(); + if (this._data.eventsFunctionsExtensions) { + for (const extension of this._data.eventsFunctionsExtensions) { + for (const eventsBasedObject of extension.eventsBasedObjects) { + this._eventsBasedObjectDatas.set( + extension.name + '::' + eventsBasedObject.name, + eventsBasedObject + ); + } + } + } + if (this.isUsingGDevelopDevelopmentEnvironment()) { logger.info( 'This game will run on the development version of GDevelop APIs.' @@ -251,6 +264,17 @@ namespace gdjs { return this._data; } + getEventsBasedObjectData(type: string): EventsBasedObjectData | null { + const eventsBasedObjectData = this._eventsBasedObjectDatas.get(type); + if (!eventsBasedObjectData) { + logger.error( + 'The game has no events-based object of the type "' + type + '"' + ); + return null; + } + return eventsBasedObjectData; + } + /** * Get the data associated to a scene. * diff --git a/GDJS/Runtime/runtimeobject.ts b/GDJS/Runtime/runtimeobject.ts index 73890fc090..d39e5887c3 100644 --- a/GDJS/Runtime/runtimeobject.ts +++ b/GDJS/Runtime/runtimeobject.ts @@ -167,7 +167,7 @@ namespace gdjs { readonly id: integer; private destroyCallbacks = new Set<() => void>(); - _runtimeScene: gdjs.RuntimeScene; + _runtimeScene: gdjs.RuntimeInstanceContainer; /** * An optional UUID associated to the object to be used @@ -209,12 +209,15 @@ namespace gdjs { * @param runtimeScene The scene the object belongs to.. * @param objectData The initial properties of the object. */ - constructor(runtimeScene: gdjs.RuntimeScene, objectData: ObjectData) { + constructor( + instanceContainer: gdjs.RuntimeInstanceContainer, + objectData: ObjectData & any + ) { this.name = objectData.name || ''; this.type = objectData.type || ''; this._nameId = RuntimeObject.getNameIdentifier(this.name); - this.id = runtimeScene.createNewUniqueId(); - this._runtimeScene = runtimeScene; + this.id = instanceContainer.getScene().createNewUniqueId(); + this._runtimeScene = instanceContainer; this._defaultHitBoxes.push(gdjs.Polygon.createRectangle(0, 0)); this.hitBoxes = this._defaultHitBoxes; this._variables = new gdjs.VariablesContainer( @@ -234,7 +237,7 @@ namespace gdjs { for (let i = 0, len = objectData.behaviors.length; i < len; ++i) { const autoData = objectData.behaviors[i]; const Ctor = gdjs.getBehaviorConstructor(autoData.type); - this._behaviors.push(new Ctor(runtimeScene, autoData, this)); + this._behaviors.push(new Ctor(instanceContainer, autoData, this)); this._behaviorsTable.put(autoData.name, this._behaviors[i]); } this._timers = new Hashtable(); @@ -335,6 +338,8 @@ namespace gdjs { this._timers.clear(); this.destroyCallbacks.clear(); + + this.invalidateHitboxes(); } static supportsReinitialization = false; @@ -345,9 +350,9 @@ namespace gdjs { * * Objects can have different elapsed time if they are on layers with different time scales. * - * @param runtimeScene The RuntimeScene the object belongs to (deprecated - can be omitted). + * @param instanceContainer The instance container the object belongs to (deprecated - can be omitted). */ - getElapsedTime(runtimeScene?: gdjs.RuntimeScene): float { + getElapsedTime(instanceContainer?: gdjs.RuntimeInstanceContainer): float { const theLayer = this._runtimeScene.getLayer(this.layer); return theLayer.getElapsedTime(); } @@ -355,21 +360,35 @@ namespace gdjs { /** * The gdjs.RuntimeScene the object belongs to. */ - getRuntimeScene(): RuntimeScene { + getParent(): gdjs.RuntimeInstanceContainer { + return this._runtimeScene; + } + + /** + * The gdjs.RuntimeScene the object belongs to. + */ + getRuntimeScene(): gdjs.RuntimeScene { + return this._runtimeScene.getScene(); + } + + /** + * The container the object belongs to. + */ + getInstanceContainer(): gdjs.RuntimeInstanceContainer { return this._runtimeScene; } /** * Called once during the game loop, before events and rendering. - * @param runtimeScene The gdjs.RuntimeScene the object belongs to. + * @param instanceContainer The container the object belongs to. */ - update(runtimeScene: gdjs.RuntimeScene): void {} + update(instanceContainer: gdjs.RuntimeInstanceContainer): void {} /** * Called once during the game loop, after events and before rendering. - * @param runtimeScene The gdjs.RuntimeScene the object belongs to. + * @param instanceContainer The container the object belongs to. */ - updatePreRender(runtimeScene: gdjs.RuntimeScene): void {} + updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer): void {} /** * Called when the object is created from an initial instance at the startup of the scene.
@@ -401,11 +420,11 @@ namespace gdjs { * Remove an object from a scene. * * Do not change/redefine this method. Instead, redefine the onDestroyFromScene method. - * @param runtimeScene The RuntimeScene owning the object. + * @param instanceContainer The container owning the object. */ - deleteFromScene(runtimeScene: gdjs.RuntimeScene): void { + deleteFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { if (this._livingOnScene) { - runtimeScene.markObjectForDeletion(this); + instanceContainer.markObjectForDeletion(this); this._livingOnScene = false; } } @@ -423,10 +442,10 @@ namespace gdjs { * is being unloaded). If you redefine this function, **make sure to call the original method** * (`RuntimeObject.prototype.onDestroyFromScene.call(this, runtimeScene);`). * - * @param runtimeScene The scene owning the object. + * @param instanceContainer The container owning the object. */ - onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void { - const theLayer = runtimeScene.getLayer(this.layer); + onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void { + const theLayer = instanceContainer.getLayer(this.layer); const rendererObject = this.getRendererObject(); if (rendererObject) { theLayer.getRenderer().removeRendererObject(rendererObject); @@ -515,7 +534,21 @@ namespace gdjs { return; } this.x = x; + this.invalidateHitboxes(); + } + + /** + * Send a signal that the object hitboxes are no longer up to date. + * + * The signal is propagated to parents so + * {@link gdjs.RuntimeObject.hitBoxesDirty} should never be modified + * directly. + */ + invalidateHitboxes(): void { + // TODO EBO Check that no community extension set hitBoxesDirty to true + // directly. this.hitBoxesDirty = true; + this._runtimeScene.onChildrenLocationChanged(); } /** @@ -537,7 +570,7 @@ namespace gdjs { return; } this.y = y; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -593,10 +626,15 @@ namespace gdjs { ); } + /** + * @param angle The targeted direction angle. + * @param speed The rotation speed. + * @param instanceContainer The container the object belongs to (deprecated - can be omitted). + */ rotateTowardAngle( angle: float, speed: float, - runtimeScene: gdjs.RuntimeScene + instanceContainer?: gdjs.RuntimeInstanceContainer ): void { if (speed === 0) { this.setAngle(angle); @@ -609,10 +647,7 @@ namespace gdjs { const diffWasPositive = angularDiff >= 0; let newAngle = this.getAngle() + - ((diffWasPositive ? -1.0 : 1.0) * - speed * - this.getElapsedTime(runtimeScene)) / - 1000; + ((diffWasPositive ? -1.0 : 1.0) * speed * this.getElapsedTime()) / 1000; if ( // @ts-ignore @@ -637,12 +672,13 @@ namespace gdjs { * Rotate the object at the given speed * * @param speed The speed, in degrees per second. - * @param runtimeScene The scene where the object is displayed. + * @param instanceContainer The container the object belongs to (deprecated - can be omitted). */ - rotate(speed: float, runtimeScene: gdjs.RuntimeScene): void { - this.setAngle( - this.getAngle() + (speed * this.getElapsedTime(runtimeScene)) / 1000 - ); + rotate( + speed: float, + instanceContainer?: gdjs.RuntimeInstanceContainer + ): void { + this.setAngle(this.getAngle() + (speed * this.getElapsedTime()) / 1000); } /** @@ -655,7 +691,7 @@ namespace gdjs { return; } this.angle = angle; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -1620,18 +1656,22 @@ namespace gdjs { /** * Call each behavior stepPreEvents method. */ - stepBehaviorsPreEvents(runtimeScene): void { + stepBehaviorsPreEvents( + instanceContainer: gdjs.RuntimeInstanceContainer + ): void { for (let i = 0, len = this._behaviors.length; i < len; ++i) { - this._behaviors[i].stepPreEvents(runtimeScene); + this._behaviors[i].stepPreEvents(instanceContainer); } } /** * Call each behavior stepPostEvents method. */ - stepBehaviorsPostEvents(runtimeScene): void { + stepBehaviorsPostEvents( + instanceContainer: gdjs.RuntimeInstanceContainer + ): void { for (let i = 0, len = this._behaviors.length; i < len; ++i) { - this._behaviors[i].stepPostEvents(runtimeScene); + this._behaviors[i].stepPostEvents(instanceContainer); } } @@ -1862,7 +1902,7 @@ namespace gdjs { * @return true if the object was moved */ separateFromObjects( - objects: RuntimeObject[], + objects: gdjs.RuntimeObject[], ignoreTouchingEdges: boolean ): boolean { let moveXArray: Array = separateFromObjectsStatics.moveXArray; @@ -2402,12 +2442,18 @@ namespace gdjs { * * @return true if the cursor, or any touch, is on the object. */ - cursorOnObject(runtimeScene: RuntimeScene): boolean { - const inputManager = runtimeScene.getGame().getInputManager(); - const layer = runtimeScene.getLayer(this.layer); + cursorOnObject(instanceContainer: gdjs.RuntimeInstanceContainer): boolean { + const workingPoint: FloatPoint = gdjs.staticArray( + RuntimeObject.prototype.cursorOnObject + ) as FloatPoint; + workingPoint.length = 2; + const inputManager = instanceContainer.getGame().getInputManager(); + const layer = instanceContainer.getLayer(this.layer); const mousePos = layer.convertCoords( inputManager.getMouseX(), - inputManager.getMouseY() + inputManager.getMouseY(), + 0, + workingPoint ); if (this.insideObject(mousePos[0], mousePos[1])) { return true; @@ -2416,7 +2462,9 @@ namespace gdjs { for (let i = 0; i < touchIds.length; ++i) { const touchPos = layer.convertCoords( inputManager.getTouchX(touchIds[i]), - inputManager.getTouchY(touchIds[i]) + inputManager.getTouchY(touchIds[i]), + 0, + workingPoint ); if (this.insideObject(touchPos[0], touchPos[1])) { return true; diff --git a/GDJS/Runtime/runtimescene.ts b/GDJS/Runtime/runtimescene.ts index 57f6c22800..8914201ca3 100644 --- a/GDJS/Runtime/runtimescene.ts +++ b/GDJS/Runtime/runtimescene.ts @@ -10,21 +10,14 @@ namespace gdjs { /** * A scene being played, containing instances of objects rendered on screen. */ - export class RuntimeScene { + export class RuntimeScene extends gdjs.RuntimeInstanceContainer { _eventsFunction: null | ((runtimeScene: RuntimeScene) => void) = null; - _instances: Hashtable; - //Contains the instances living on the scene - _instancesCache: Hashtable; - - //Used to recycle destroyed instance instead of creating new ones. - _objects: Hashtable; - - //Contains the objects data stored in the project - _objectsCtor: Hashtable; - _layers: Hashtable; + // TODO EBO move behavior shared data up to the instances container. + /** Contains the objects data stored in the project */ _initialBehaviorSharedData: Hashtable; _renderer: RuntimeSceneRenderer; + _debuggerRenderer: gdjs.DebuggerRenderer; _variables: gdjs.VariablesContainer; _runtimeGame: gdjs.RuntimeGame; _lastId: integer = 0; @@ -32,92 +25,77 @@ namespace gdjs { _timeManager: TimeManager; _gameStopRequested: boolean = false; _requestedScene: string = ''; - _isLoaded: boolean = false; private _asyncTasksManager = new gdjs.AsyncTasksManager(); - // True if loadFromScene was called and the scene is being played. + /** True if loadFromScene was called and the scene is being played. */ + _isLoaded: boolean = false; + /** True in the first frame after resuming the paused scene */ _isJustResumed: boolean = false; - // True in the first frame after resuming the paused scene _requestedChange: SceneChangeRequest; + /** Black background by default. */ _backgroundColor: integer = 0; - _allInstancesList: gdjs.RuntimeObject[] = []; _onceTriggers: OnceTriggers; - _layersCameraCoordinates: Record = {}; - _instancesRemoved: gdjs.RuntimeObject[] = []; _profiler: gdjs.Profiler | null = null; - // Options for the debug draw: - _debugDrawEnabled: boolean = false; - _debugDrawShowHiddenInstances: boolean = false; - _debugDrawShowPointsNames: boolean = false; - _debugDrawShowCustomPoints: boolean = false; - // Set to `new gdjs.Profiler()` to have profiling done on the scene. _onProfilerStopped: null | ((oldProfiler: gdjs.Profiler) => void) = null; + _cachedGameResolutionWidth: integer; + _cachedGameResolutionHeight: integer; + /** * @param runtimeGame The game associated to this scene. */ constructor(runtimeGame: gdjs.RuntimeGame) { - this._instances = new Hashtable(); - this._instancesCache = new Hashtable(); - this._objects = new Hashtable(); - this._objectsCtor = new Hashtable(); - this._layers = new Hashtable(); + super(); this._initialBehaviorSharedData = new Hashtable(); + this._runtimeGame = runtimeGame; + this._variables = new gdjs.VariablesContainer(); + this._timeManager = new gdjs.TimeManager(); + this._onceTriggers = new gdjs.OnceTriggers(); + this._requestedChange = SceneChangeRequest.CONTINUE; + this._cachedGameResolutionWidth = runtimeGame + ? runtimeGame.getGameResolutionWidth() + : 0; + this._cachedGameResolutionHeight = runtimeGame + ? runtimeGame.getGameResolutionHeight() + : 0; + this._renderer = new gdjs.RuntimeSceneRenderer( this, - // @ts-ignore + // @ts-ignore This is needed because of test. They should mock RuntimeGame instead. runtimeGame ? runtimeGame.getRenderer() : null ); - this._variables = new gdjs.VariablesContainer(); - this._runtimeGame = runtimeGame; - this._timeManager = new gdjs.TimeManager(); - this._requestedChange = SceneChangeRequest.CONTINUE; + this._debuggerRenderer = new gdjs.DebuggerRenderer(this); // What to do after the frame is rendered. - // Black background by default. - - //An array used to create a list of all instance when necessary ( see _constructListOfAllInstances ) - this._onceTriggers = new gdjs.OnceTriggers(); - - //The instances removed from the scene and waiting to be sent to the cache. - // The callback function to call when the profiler is stopped. this.onGameResolutionResized(); } - /** - * Activate or deactivate the debug visualization for collisions and points. - */ - enableDebugDraw( - enableDebugDraw: boolean, - showHiddenInstances: boolean, - showPointsNames: boolean, - showCustomPoints: boolean - ): void { - if (this._debugDrawEnabled && !enableDebugDraw) { - this.getRenderer().clearDebugDraw(); - } - - this._debugDrawEnabled = enableDebugDraw; - this._debugDrawShowHiddenInstances = showHiddenInstances; - this._debugDrawShowPointsNames = showPointsNames; - this._debugDrawShowCustomPoints = showCustomPoints; - } - /** * Should be called when the canvas where the scene is rendered has been resized. * See gdjs.RuntimeGame.startGameLoop in particular. */ onGameResolutionResized() { + const oldGameResolutionOriginX = this.getViewportOriginX(); + const oldGameResolutionOriginY = this.getViewportOriginY(); + this._cachedGameResolutionWidth = this._runtimeGame + ? this._runtimeGame.getGameResolutionWidth() + : 0; + this._cachedGameResolutionHeight = this._runtimeGame + ? this._runtimeGame.getGameResolutionHeight() + : 0; for (const name in this._layers.items) { if (this._layers.items.hasOwnProperty(name)) { /** @type gdjs.Layer */ const theLayer: gdjs.Layer = this._layers.items[name]; - theLayer.onGameResolutionResized(); + theLayer.onGameResolutionResized( + oldGameResolutionOriginX, + oldGameResolutionOriginY + ); } } this._renderer.onGameResolutionResized(); @@ -133,6 +111,7 @@ namespace gdjs { logger.error('loadFromScene was called without a scene'); return; } + if (this._isLoaded) { this.unloadScene(); } @@ -208,75 +187,13 @@ namespace gdjs { this._timeManager.reset(); } - /** - * Check if an object is registered, meaning that instances of it can be created and lives in the scene. - * @see gdjs.RuntimeScene#registerObject - */ - isObjectRegistered(objectName: string): boolean { - return ( - this._objects.containsKey(objectName) && - this._instances.containsKey(objectName) && - this._objectsCtor.containsKey(objectName) + addLayer(layerData: LayerData) { + this._layers.put( + layerData.name, + new gdjs.RuntimeSceneLayer(layerData, this) ); } - /** - * Register a {@link gdjs.RuntimeObject} so that instances of it can be used in the scene. - * @param objectData The data for the object to register. - */ - registerObject(objectData: ObjectData) { - this._objects.put(objectData.name, objectData); - this._instances.put(objectData.name, []); - - // Cache the constructor - const Ctor = gdjs.getObjectConstructor(objectData.type); - this._objectsCtor.put(objectData.name, Ctor); - - // Also prepare a cache for recycled instances, if the object supports it. - if (Ctor.supportsReinitialization) { - this._instancesCache.put(objectData.name, []); - } - } - - /** - * Update the data of a {@link gdjs.RuntimeObject} so that instances use this when constructed. - * @param objectData The data for the object to register. - */ - updateObject(objectData: ObjectData): void { - if (!this.isObjectRegistered(objectData.name)) { - logger.warn( - 'Tried to call updateObject for an object that was not registered (' + - objectData.name + - '). Call registerObject first.' - ); - } - this._objects.put(objectData.name, objectData); - } - - // Don't erase instances, nor instances cache, or objectsCtor cache. - /** - * Unregister a {@link gdjs.RuntimeObject}. Instances will be destroyed. - * @param objectName The name of the object to unregister. - */ - unregisterObject(objectName: string) { - const instances = this._instances.get(objectName); - if (instances) { - // This is sub-optimal: markObjectForDeletion will search the instance to - // remove in instances, so cost is O(n^2), n being the number of instances. - // As we're unregistering an object which only happen during a hot-reloading, - // this is fine. - const instancesToRemove = instances.slice(); - for (let i = 0; i < instancesToRemove.length; i++) { - this.markObjectForDeletion(instancesToRemove[i]); - } - this._cacheOrClearRemovedInstances(); - } - this._objects.remove(objectName); - this._instances.remove(objectName); - this._instancesCache.remove(objectName); - this._objectsCtor.remove(objectName); - } - /** * Called when a scene is "paused", i.e it will be not be rendered again * for some time, until it's resumed or unloaded. @@ -284,9 +201,9 @@ namespace gdjs { onPause() { // Notify the objects that the scene is being paused. Objects should not // do anything special, but some object renderers might want to know about this. - this._constructListOfAllInstances(); - for (let i = 0, len = this._allInstancesList.length; i < len; ++i) { - const object = this._allInstancesList[i]; + const allInstancesList = this.getAdhocListOfAllInstances(); + for (let i = 0, len = allInstancesList.length; i < len; ++i) { + const object = allInstancesList[i]; object.onScenePaused(this); } @@ -304,9 +221,9 @@ namespace gdjs { // Notify the objects that the scene is being resumed. Objects should not // do anything special, but some object renderers might want to know about this. - this._constructListOfAllInstances(); - for (let i = 0, len = this._allInstancesList.length; i < len; ++i) { - const object = this._allInstancesList[i]; + const allInstancesList = this.getAdhocListOfAllInstances(); + for (let i = 0, len = allInstancesList.length; i < len; ++i) { + const object = allInstancesList[i]; object.onSceneResumed(this); } @@ -334,9 +251,9 @@ namespace gdjs { } // Notify the objects they are being destroyed - this._constructListOfAllInstances(); - for (let i = 0, len = this._allInstancesList.length; i < len; ++i) { - const object = this._allInstancesList[i]; + const allInstancesList = this.getAdhocListOfAllInstances(); + for (let i = 0, len = allInstancesList.length; i < len; ++i) { + const object = allInstancesList[i]; object.onDestroyFromScene(this); } @@ -352,96 +269,22 @@ namespace gdjs { gdjs.callbacksRuntimeSceneUnloaded[i](this); } - // It should not be necessary to reset these variables, but this help - // ensuring that all memory related to the RuntimeScene is released immediately. - this._layers = new Hashtable(); - this._variables = new gdjs.VariablesContainer(); - this._initialBehaviorSharedData = new Hashtable(); - this._objects = new Hashtable(); - this._instances = new Hashtable(); - this._instancesCache = new Hashtable(); - this._eventsFunction = null; - this._objectsCtor = new Hashtable(); - this._allInstancesList = []; - this._instancesRemoved = []; - this._lastId = 0; + this._destroy(); - //@ts-ignore We are deleting the object - this._onceTriggers = null; this._isLoaded = false; this.onGameResolutionResized(); } - /** - * Create objects from initial instances data (for example, the initial instances - * of the scene or the instances of an external layout). - * - * @param data The instances data - * @param xPos The offset on X axis - * @param yPos The offset on Y axis - * @param trackByPersistentUuid If true, objects are tracked by setting their `persistentUuid` - * to the same as the associated instance. Useful for hot-reloading when instances are changed. - */ - createObjectsFrom( - data: InstanceData[], - xPos: float, - yPos: float, - trackByPersistentUuid: boolean - ) { - for (let i = 0, len = data.length; i < len; ++i) { - const instanceData = data[i]; - const objectName = instanceData.name; - const newObject = this.createObject(objectName); - if (newObject !== null) { - if (trackByPersistentUuid) { - // Give the object the same persistentUuid as the instance, so that - // it can be hot-reloaded. - newObject.persistentUuid = instanceData.persistentUuid || null; - } - newObject.setPosition(instanceData.x + xPos, instanceData.y + yPos); - newObject.setZOrder(instanceData.zOrder); - newObject.setAngle(instanceData.angle); - newObject.setLayer(instanceData.layer); - newObject - .getVariables() - .initFrom(instanceData.initialVariables, true); - newObject.extraInitializationFromInitialInstance(instanceData); - } - } - } - - /** - * Set the default Z order for each layer, which is the highest Z order found on each layer. - * Useful as it ensures that instances created from events are, by default, shown in front - * of other instances. - */ - private _setLayerDefaultZOrders() { - if ( - this._runtimeGame.getGameData().properties - .useDeprecatedZeroAsDefaultZOrder - ) { - // Deprecated option to still support games that were made considered 0 as the - // default Z order for all layers. - return; - } - const layerHighestZOrders: Record = {}; - const allInstances = this.getAdhocListOfAllInstances(); - for (let i = 0, len = allInstances.length; i < len; ++i) { - const object = allInstances[i]; - let layerName = object.getLayer(); - const zOrder = object.getZOrder(); - if ( - layerHighestZOrders[layerName] === undefined || - layerHighestZOrders[layerName] < zOrder - ) { - layerHighestZOrders[layerName] = zOrder; - } - } - for (let layerName in layerHighestZOrders) { - this.getLayer(layerName).setDefaultZOrder( - layerHighestZOrders[layerName] + 1 - ); - } + _destroy() { + // It should not be necessary to reset these variables, but this help + // ensuring that all memory related to the RuntimeScene is released immediately. + super._destroy(); + this._variables = new gdjs.VariablesContainer(); + this._initialBehaviorSharedData = new Hashtable(); + this._eventsFunction = null; + this._lastId = 0; + // @ts-ignore We are deleting the object + this._onceTriggers = null; } /** @@ -557,8 +400,8 @@ namespace gdjs { // Set to true to enable debug rendering (look for the implementation in the renderer // to see what is rendered). if (this._debugDrawEnabled) { - this.getRenderer().renderDebugDraw( - this._allInstancesList, + this._debuggerRenderer.renderDebugDraw( + this.getAdhocListOfAllInstances(), this._debugDrawShowHiddenInstances, this._debugDrawShowPointsNames, this._debugDrawShowCustomPoints @@ -583,38 +426,6 @@ namespace gdjs { this._renderer.render(); } - _updateLayersCameraCoordinates(scale: float) { - this._layersCameraCoordinates = this._layersCameraCoordinates || {}; - for (const name in this._layers.items) { - if (this._layers.items.hasOwnProperty(name)) { - const theLayer = this._layers.items[name]; - this._layersCameraCoordinates[name] = this._layersCameraCoordinates[ - name - ] || [0, 0, 0, 0]; - this._layersCameraCoordinates[name][0] = - theLayer.getCameraX() - (theLayer.getCameraWidth() / 2) * scale; - this._layersCameraCoordinates[name][1] = - theLayer.getCameraY() - (theLayer.getCameraHeight() / 2) * scale; - this._layersCameraCoordinates[name][2] = - theLayer.getCameraX() + (theLayer.getCameraWidth() / 2) * scale; - this._layersCameraCoordinates[name][3] = - theLayer.getCameraY() + (theLayer.getCameraHeight() / 2) * scale; - } - } - } - - /** - * Called to update effects of layers before rendering. - */ - _updateLayersPreRender() { - for (const name in this._layers.items) { - if (this._layers.items.hasOwnProperty(name)) { - const layer = this._layers.items[name]; - layer.updatePreRender(this); - } - } - } - /** * Called to update visibility of the renderers of objects * rendered on the scene ("culling"), update effects (of visible objects) @@ -625,24 +436,7 @@ namespace gdjs { */ _updateObjectsPreRender() { if (this._timeManager.isFirstFrame()) { - this._constructListOfAllInstances(); - for (let i = 0, len = this._allInstancesList.length; i < len; ++i) { - const object = this._allInstancesList[i]; - const rendererObject = object.getRendererObject(); - if (rendererObject) { - rendererObject.visible = !object.isHidden(); - - // Update effects, only for visible objects. - if (rendererObject.visible) { - this._runtimeGame - .getEffectsManager() - .updatePreRender(object.getRendererEffects(), object); - } - } - - // Perform pre-render update. - object.updatePreRender(this); - } + super._updateObjectsPreRender(); return; } else { // After first frame, optimise rendering by setting only objects @@ -656,9 +450,9 @@ namespace gdjs { // instead. // - objects having effects rendering outside of their visibility AABB. this._updateLayersCameraCoordinates(2); - this._constructListOfAllInstances(); - for (let i = 0, len = this._allInstancesList.length; i < len; ++i) { - const object = this._allInstancesList[i]; + const allInstancesList = this.getAdhocListOfAllInstances(); + for (let i = 0, len = allInstancesList.length; i < len; ++i) { + const object = allInstancesList[i]; const rendererObject = object.getRendererObject(); if (rendererObject) { if (object.isHidden()) { @@ -703,98 +497,6 @@ namespace gdjs { } } - /** - * Empty the list of the removed objects:
- * When an object is removed from the scene, it is still kept in the _instancesRemoved member - * of the RuntimeScene.
- * This method should be called regularly (after events or behaviors steps) so as to clear this list - * and allows the removed objects to be cached (or destroyed if the cache is full).
- * The removed objects could not be sent directly to the cache, as events may still be using them after - * removing them from the scene for example. - */ - _cacheOrClearRemovedInstances() { - for (let k = 0, lenk = this._instancesRemoved.length; k < lenk; ++k) { - // Cache the instance to recycle it into a new instance later. - // If the object does not support recycling, the cache won't be defined. - const cache = this._instancesCache.get( - this._instancesRemoved[k].getName() - ); - if (cache) { - if (cache.length < 128) { - cache.push(this._instancesRemoved[k]); - } - } - } - this._instancesRemoved.length = 0; - } - - /** - * Tool function filling _allInstancesList member with all the living object instances. - */ - _constructListOfAllInstances() { - let currentListSize = 0; - for (const name in this._instances.items) { - if (this._instances.items.hasOwnProperty(name)) { - const list = this._instances.items[name]; - const oldSize = currentListSize; - currentListSize += list.length; - for (let j = 0, lenj = list.length; j < lenj; ++j) { - if (oldSize + j < this._allInstancesList.length) { - this._allInstancesList[oldSize + j] = list[j]; - } else { - this._allInstancesList.push(list[j]); - } - } - } - } - this._allInstancesList.length = currentListSize; - } - - /** - * Update the objects before launching the events. - */ - _updateObjectsPreEvents() { - //It is *mandatory* to create and iterate on a external list of all objects, as the behaviors - //may delete the objects. - this._constructListOfAllInstances(); - for (let i = 0, len = this._allInstancesList.length; i < len; ++i) { - const obj = this._allInstancesList[i]; - const elapsedTime = obj.getElapsedTime(this); - if (!obj.hasNoForces()) { - const averageForce = obj.getAverageForce(); - const elapsedTimeInSeconds = elapsedTime / 1000; - obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds); - obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds); - obj.update(this); - obj.updateForces(elapsedTimeInSeconds); - } else { - obj.update(this); - } - obj.updateTimers(elapsedTime); - this._allInstancesList[i].stepBehaviorsPreEvents(this); - } - - //Some behaviors may have request objects to be deleted. - this._cacheOrClearRemovedInstances(); - } - - /** - * Update the objects (update positions, time management...) - */ - _updateObjectsPostEvents() { - this._cacheOrClearRemovedInstances(); - - //It is *mandatory* to create and iterate on a external list of all objects, as the behaviors - //may delete the objects. - this._constructListOfAllInstances(); - for (let i = 0, len = this._allInstancesList.length; i < len; ++i) { - this._allInstancesList[i].stepBehaviorsPostEvents(this); - } - - //Some behaviors may have request objects to be deleted. - this._cacheOrClearRemovedInstances(); - } - /** * Change the background color, by setting the RGB components. * Internally, the color is stored as an hexadecimal number. @@ -822,121 +524,6 @@ namespace gdjs { return this._name; } - /** - * Update the objects positions according to their forces - */ - updateObjectsForces(): void { - for (const name in this._instances.items) { - if (this._instances.items.hasOwnProperty(name)) { - const list = this._instances.items[name]; - for (let j = 0, listLen = list.length; j < listLen; ++j) { - const obj = list[j]; - if (!obj.hasNoForces()) { - const averageForce = obj.getAverageForce(); - const elapsedTimeInSeconds = obj.getElapsedTime(this) / 1000; - obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds); - obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds); - obj.updateForces(elapsedTimeInSeconds); - } - } - } - } - } - - /** - * Add an object to the instances living on the scene. - * @param obj The object to be added. - */ - addObject(obj: RuntimeObject) { - if (!this._instances.containsKey(obj.name)) { - this._instances.put(obj.name, []); - } - this._instances.get(obj.name).push(obj); - } - - /** - * Get all the instances of the object called name. - * @param name Name of the object for which the instances must be returned. - * @return The list of objects with the given name - */ - getObjects(name: string): gdjs.RuntimeObject[] { - if (!this._instances.containsKey(name)) { - logger.info( - 'RuntimeScene.getObjects: No instances called "' + - name + - '"! Adding it.' - ); - this._instances.put(name, []); - } - return this._instances.get(name); - } - - /** - * Create a new object from its name. The object is also added to the instances - * living on the scene ( No need to call RuntimeScene.addObject ) - * @param objectName The name of the object to be created - * @return The created object - */ - createObject(objectName: string): gdjs.RuntimeObject | null { - if ( - !this._objectsCtor.containsKey(objectName) || - !this._objects.containsKey(objectName) - ) { - return null; - } - - //There is no such object in this scene. - - // Create a new object using the object constructor (cached during loading) - // and the stored object's data: - const cache = this._instancesCache.get(objectName); - const ctor = this._objectsCtor.get(objectName); - let obj; - if (!cache || cache.length === 0) { - obj = new ctor(this, this._objects.get(objectName)); - } else { - // Reuse an objet destroyed before. If there is an object in the cache, - // then it means it does support reinitialization. - obj = cache.pop(); - obj.reinitialize(this._objects.get(objectName)); - } - this.addObject(obj); - return obj; - } - - /** - * Must be called whenever an object must be removed from the scene. - * @param obj The object to be removed. - */ - markObjectForDeletion(obj: gdjs.RuntimeObject) { - //Add to the objects removed list. - //The objects will be sent to the instances cache or really deleted from memory later. - if (this._instancesRemoved.indexOf(obj) === -1) { - this._instancesRemoved.push(obj); - } - - //Delete from the living instances. - if (this._instances.containsKey(obj.getName())) { - const objId = obj.id; - const allInstances = this._instances.get(obj.getName()); - for (let i = 0, len = allInstances.length; i < len; ++i) { - if (allInstances[i].id == objId) { - allInstances.splice(i, 1); - break; - } - } - } - - //Notify the object it was removed from the scene - obj.onDestroyFromScene(this); - - // Notify the global callbacks - for (let j = 0; j < gdjs.callbacksObjectDeletedFromScene.length; ++j) { - gdjs.callbacksObjectDeletedFromScene[j](this, obj); - } - return; - } - /** * Create an identifier for a new object of the scene. */ @@ -945,20 +532,61 @@ namespace gdjs { return this._lastId; } - /** - * Get the renderer associated to the RuntimeScene. - */ - getRenderer() { + getRenderer(): gdjs.RuntimeScenePixiRenderer { return this._renderer; } - /** - * Get the runtimeGame associated to the RuntimeScene. - */ + getDebuggerRenderer() { + return this._debuggerRenderer; + } + getGame() { return this._runtimeGame; } + getScene() { + return this; + } + + getViewportWidth(): float { + return this._cachedGameResolutionWidth; + } + + getViewportHeight(): float { + return this._cachedGameResolutionHeight; + } + + getViewportOriginX(): float { + return this._cachedGameResolutionWidth / 2; + } + + getViewportOriginY(): float { + return this._cachedGameResolutionHeight / 2; + } + + convertCoords(x: float, y: float, result: FloatPoint): FloatPoint { + // The result parameter used to be optional. + const point = result || [0, 0]; + point[0] = x; + point[1] = y; + return point; + } + + convertInverseCoords( + sceneX: float, + sceneY: float, + result: FloatPoint + ): FloatPoint { + const point = result || [0, 0]; + point[0] = sceneX; + point[1] = sceneY; + return point; + } + + onChildrenLocationChanged(): void { + // Scenes don't maintain bounds. + } + /** * Get the variables of the runtimeScene. * @return The container holding the variables of the scene. @@ -993,72 +621,6 @@ namespace gdjs { this._initialBehaviorSharedData.put(name, sharedData); } - /** - * Get the layer with the given name - * @param name The name of the layer - * @returns The layer, or the base layer if not found - */ - getLayer(name: string): gdjs.Layer { - if (this._layers.containsKey(name)) { - return this._layers.get(name); - } - return this._layers.get(''); - } - - /** - * Check if a layer exists - * @param name The name of the layer - */ - hasLayer(name: string): boolean { - return this._layers.containsKey(name); - } - - /** - * Add a layer. - * @param layerData The data to construct the layer - */ - addLayer(layerData: LayerData) { - this._layers.put(layerData.name, new gdjs.Layer(layerData, this)); - } - - /** - * Remove a layer. All {@link gdjs.RuntimeObject} on this layer will - * be moved back to the base layer. - * @param layerName The name of the layer to remove - */ - removeLayer(layerName: string) { - const allInstances = this.getAdhocListOfAllInstances(); - for (let i = 0; i < allInstances.length; ++i) { - const runtimeObject = allInstances[i]; - if (runtimeObject.getLayer() === layerName) { - runtimeObject.setLayer(''); - } - } - this._layers.remove(layerName); - } - - /** - * Change the position of a layer. - * - * @param layerName The name of the layer to reorder - * @param index The new position in the list of layers - */ - setLayerIndex(layerName: string, index: integer): void { - const layer: gdjs.Layer = this._layers.get(layerName); - if (!layer) { - return; - } - this._renderer.setLayerIndex(layer, index); - } - - /** - * Fill the array passed as argument with the names of all layers - * @param result The array where to put the layer names - */ - getAllLayerNames(result: string[]) { - this._layers.keys(result); - } - /** * Get the TimeManager of the scene. * @return The gdjs.TimeManager of the scene. @@ -1067,6 +629,14 @@ namespace gdjs { return this._timeManager; } + /** + * Return the time elapsed since the last frame, + * in milliseconds, for objects on the layer. + */ + getElapsedTime(): float { + return this._timeManager.getElapsedTime(); + } + /** * Shortcut to get the SoundManager of the game. * @return The gdjs.SoundManager of the game. @@ -1112,7 +682,7 @@ namespace gdjs { /** * Get the profiler associated with the scene, or null if none. */ - getProfiler() { + getProfiler(): gdjs.Profiler | null { return this._profiler; } @@ -1152,32 +722,6 @@ namespace gdjs { return this._onceTriggers; } - /** - * Get a list of all gdjs.RuntimeObject living on the scene. - * You should not, normally, need this method at all. It's only to be used - * in exceptional use cases where you need to loop through all objects, - * and it won't be performant. - * - * @returns The list of all runtime objects on the scnee - */ - getAdhocListOfAllInstances(): gdjs.RuntimeObject[] { - this._constructListOfAllInstances(); - return this._allInstancesList; - } - - /** - * Return the number of instances of the specified object living on the scene. - * @param objectName The object name for which instances must be counted. - */ - getInstancesCountOnScene(objectName: string): integer { - const instances = this._instances.get(objectName); - if (instances) { - return instances.length; - } - - return 0; - } - /** * Check if the scene was just resumed. * This is true during the first frame after the scene has been unpaused. diff --git a/GDJS/Runtime/spriteruntimeobject.ts b/GDJS/Runtime/spriteruntimeobject.ts index 67f283a426..396cee0c47 100644 --- a/GDJS/Runtime/spriteruntimeobject.ts +++ b/GDJS/Runtime/spriteruntimeobject.ts @@ -201,7 +201,10 @@ namespace gdjs { loop: boolean; frames: SpriteAnimationFrame[] = []; - constructor(imageManager, directionData) { + constructor( + imageManager: gdjs.PixiImageManager, + directionData: SpriteDirectionData + ) { this.timeBetweenFrames = directionData ? directionData.timeBetweenFrames : 1.0; @@ -247,7 +250,10 @@ namespace gdjs { name: string; directions: gdjs.SpriteAnimationDirection[] = []; - constructor(imageManager, animData) { + constructor( + imageManager: gdjs.PixiImageManager, + animData: SpriteAnimationData + ) { this.hasMultipleDirections = !!animData.useMultipleDirections; this.name = animData.name || ''; this.reinitialize(imageManager, animData); @@ -282,9 +288,6 @@ namespace gdjs { /** * The SpriteRuntimeObject represents an object that can display images. - * - * @param runtimeScene The scene the object belongs to - * @param spriteObjectData The object data used to initialize the object */ export class SpriteRuntimeObject extends gdjs.RuntimeObject { _currentAnimation: number = 0; @@ -313,21 +316,30 @@ namespace gdjs { */ _animationFrame: gdjs.SpriteAnimationFrame | null = null; _renderer: gdjs.SpriteRuntimeObjectRenderer; - hitBoxesDirty: any; _animationFrameDirty: any; - constructor(runtimeScene, spriteObjectData) { - super(runtimeScene, spriteObjectData); + /** + * @param instanceContainer The container the object belongs to + * @param spriteObjectData The object data used to initialize the object + */ + constructor( + instanceContainer: gdjs.RuntimeInstanceContainer, + spriteObjectData: ObjectData & SpriteObjectDataType + ) { + super(instanceContainer, spriteObjectData); this._updateIfNotVisible = !!spriteObjectData.updateIfNotVisible; for (let i = 0, len = spriteObjectData.animations.length; i < len; ++i) { this._animations.push( new gdjs.SpriteAnimation( - runtimeScene.getGame().getImageManager(), + instanceContainer.getGame().getImageManager(), spriteObjectData.animations[i] ) ); } - this._renderer = new gdjs.SpriteRuntimeObjectRenderer(this, runtimeScene); + this._renderer = new gdjs.SpriteRuntimeObjectRenderer( + this, + instanceContainer + ); this._updateAnimationFrame(); // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. @@ -336,7 +348,7 @@ namespace gdjs { reinitialize(spriteObjectData: SpriteObjectData) { super.reinitialize(spriteObjectData); - const runtimeScene = this._runtimeScene; + const instanceContainer = this.getInstanceContainer(); this._currentAnimation = 0; this._currentDirection = 0; this._currentFrame = 0; @@ -355,13 +367,13 @@ namespace gdjs { const animData = spriteObjectData.animations[i]; if (i < this._animations.length) { this._animations[i].reinitialize( - runtimeScene.getGame().getImageManager(), + instanceContainer.getGame().getImageManager(), animData ); } else { this._animations.push( new gdjs.SpriteAnimation( - runtimeScene.getGame().getImageManager(), + instanceContainer.getGame().getImageManager(), animData ) ); @@ -371,7 +383,7 @@ namespace gdjs { //Make sure to delete already existing animations which are not used anymore. this._animationFrame = null; - this._renderer.reinitialize(this, runtimeScene); + this._renderer.reinitialize(this, instanceContainer); this._updateAnimationFrame(); // *ALWAYS* call `this.onCreated()` at the very end of your object reinitialize method. @@ -382,19 +394,19 @@ namespace gdjs { oldObjectData: SpriteObjectData, newObjectData: SpriteObjectData ): boolean { - const runtimeScene = this._runtimeScene; + const instanceContainer = this.getInstanceContainer(); let i = 0; for (const len = newObjectData.animations.length; i < len; ++i) { const animData = newObjectData.animations[i]; if (i < this._animations.length) { this._animations[i].reinitialize( - runtimeScene.getGame().getImageManager(), + instanceContainer.getGame().getImageManager(), animData ); } else { this._animations.push( new gdjs.SpriteAnimation( - runtimeScene.getGame().getImageManager(), + instanceContainer.getGame().getImageManager(), animData ) ); @@ -407,7 +419,7 @@ namespace gdjs { if (!this._animationFrame) { this.setAnimation(0); } - this.hitBoxesDirty = true; + this.invalidateHitboxes(); return true; } @@ -437,7 +449,7 @@ namespace gdjs { /** * Update the current frame of the object according to the elapsed time on the scene. */ - update(runtimeScene: gdjs.RuntimeScene): void { + update(instanceContainer: gdjs.RuntimeInstanceContainer): void { //Playing the animation of all objects including the ones outside the screen can be //costly when the scene is big with a lot of animated objects. By default, we skip //updating the object if it is not visible. @@ -463,7 +475,7 @@ namespace gdjs { //and compute nothing more. if (!direction.loop && this._currentFrame >= direction.frames.length) { } else { - const elapsedTime = this.getElapsedTime(runtimeScene) / 1000; + const elapsedTime = this.getElapsedTime() / 1000; this._frameElapsedTime += this._animationPaused ? 0 : elapsedTime * this._animationSpeedScale; @@ -493,7 +505,7 @@ namespace gdjs { this._updateAnimationFrame(); } if (oldFrame !== this._currentFrame) { - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } this._renderer.ensureUpToDate(); } @@ -502,7 +514,7 @@ namespace gdjs { * Ensure the sprite is ready to be displayed: the proper animation frame * is set and the renderer is up to date (position, angle, alpha, flip, blend mode...). */ - updatePreRender(runtimeScene: gdjs.RuntimeScene): void { + updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer): void { if (this._animationFrameDirty) { this._updateAnimationFrame(); } @@ -613,7 +625,7 @@ namespace gdjs { //TODO: This may be unnecessary. this._renderer.update(); this._animationFrameDirty = true; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } } @@ -669,7 +681,7 @@ namespace gdjs { return; } this.angle = newValue; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); this._renderer.updateAngle(); } else { newValue = newValue | 0; @@ -688,7 +700,7 @@ namespace gdjs { //TODO: This may be unnecessary. this._renderer.update(); this._animationFrameDirty = true; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } } @@ -725,7 +737,7 @@ namespace gdjs { ) { this._currentFrame = newFrame; this._animationFrameDirty = true; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } } @@ -979,7 +991,7 @@ namespace gdjs { } this.x = x; if (this._animationFrame !== null) { - this.hitBoxesDirty = true; + this.invalidateHitboxes(); this._renderer.updateX(); } } @@ -994,7 +1006,7 @@ namespace gdjs { } this.y = y; if (this._animationFrame !== null) { - this.hitBoxesDirty = true; + this.invalidateHitboxes(); this._renderer.updateY(); } } @@ -1013,7 +1025,7 @@ namespace gdjs { } this.angle = angle; this._renderer.updateAngle(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } else { angle = angle % 360; if (angle < 0) { @@ -1104,20 +1116,20 @@ namespace gdjs { return this._renderer.getColor(); } - flipX(enable) { + flipX(enable: boolean) { if (enable !== this._flippedX) { this._scaleX *= -1; this._flippedX = enable; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); this._renderer.update(); } } - flipY(enable) { + flipY(enable: boolean) { if (enable !== this._flippedY) { this._scaleY *= -1; this._flippedY = enable; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); this._renderer.update(); } } @@ -1214,7 +1226,7 @@ namespace gdjs { this._scaleX = newScale * (this._flippedX ? -1 : 1); this._scaleY = newScale * (this._flippedY ? -1 : 1); this._renderer.update(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -1231,11 +1243,11 @@ namespace gdjs { } this._scaleX = newScale * (this._flippedX ? -1 : 1); this._renderer.update(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** - * Change the scale on Y axis of the object (changing its width). + * Change the scale on Y axis of the object (changing its height). * * @param newScale The new scale (must be greater than 0). */ @@ -1248,7 +1260,7 @@ namespace gdjs { } this._scaleY = newScale * (this._flippedY ? -1 : 1); this._renderer.update(); - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } /** @@ -1284,7 +1296,7 @@ namespace gdjs { * @param scene The scene containing the object * @deprecated */ - turnTowardObject(obj, scene) { + turnTowardObject(obj: gdjs.RuntimeObject, scene: gdjs.RuntimeScene) { if (obj === null) { return; } diff --git a/GDJS/Runtime/types/project-data.d.ts b/GDJS/Runtime/types/project-data.d.ts index afd62e1d84..806f35e199 100644 --- a/GDJS/Runtime/types/project-data.d.ts +++ b/GDJS/Runtime/types/project-data.d.ts @@ -16,6 +16,7 @@ declare interface ProjectData { variables: RootVariableData[]; layouts: LayoutData[]; externalLayouts: ExternalLayoutData[]; + eventsFunctionsExtensions: EventsFunctionsExtensionData[]; } /** Object containing initial properties for all objects extending {@link gdjs.RuntimeObject}. */ @@ -27,7 +28,7 @@ declare type ObjectData = { /** The list of default variables. */ variables: Array; /** The list of default behaviors. */ - behaviors: Array; + behaviors: Array; /** The list of effects. */ effects: Array; }; @@ -84,6 +85,16 @@ declare interface LayoutData { behaviorsSharedData: BehaviorSharedData[]; } +declare interface EventsFunctionsExtensionData { + name: string; + eventsBasedObjects: EventsBasedObjectData[]; +} + +declare interface EventsBasedObjectData { + name: string; + objects: Array; +} + declare interface BehaviorSharedData { name: string; type: string; diff --git a/GDJS/tests/benchmarks/layer.js b/GDJS/tests/benchmarks/layer.js index 649e7098b7..a1d579e37d 100644 --- a/GDJS/tests/benchmarks/layer.js +++ b/GDJS/tests/benchmarks/layer.js @@ -10,8 +10,8 @@ describe('gdjs.Layer', function() { var runtimeScene = new gdjs.RuntimeScene(runtimeGame); it('benchmark convertCoords and convertInverseCoords', function() { - this.timeout(20000); - var layer = new gdjs.Layer( + this.timeout(25000); + var layer = new gdjs.RuntimeSceneLayer( { name: 'My layer', visibility: true, effects: [], @@ -29,12 +29,14 @@ describe('gdjs.Layer', function() { layer.setCameraRotation(90, 0); const benchmarkSuite = makeBenchmarkSuite(); + /** @type {FloatPoint} */ + const workingPoint = [0, 0]; benchmarkSuite .add('convertCoords', () => { - layer.convertCoords(350, 450, 0); + layer.convertCoords(350, 450, 0, workingPoint); }) .add('convertInverseCoords', () => { - layer.convertInverseCoords(350, 450, 0); + layer.convertInverseCoords(350, 450, 0, workingPoint); }); console.log(benchmarkSuite.run()); diff --git a/GDJS/tests/karma.conf.js b/GDJS/tests/karma.conf.js index 2e5f432e79..c5a905a68c 100644 --- a/GDJS/tests/karma.conf.js +++ b/GDJS/tests/karma.conf.js @@ -49,11 +49,13 @@ module.exports = function (config) { './newIDE/app/resources/GDJS/Runtime/timemanager.js', './newIDE/app/resources/GDJS/Runtime/polygon.js', './newIDE/app/resources/GDJS/Runtime/runtimeobject.js', + './newIDE/app/resources/GDJS/Runtime/RuntimeInstanceContainer.js', './newIDE/app/resources/GDJS/Runtime/runtimescene.js', './newIDE/app/resources/GDJS/Runtime/scenestack.js', './newIDE/app/resources/GDJS/Runtime/profiler.js', './newIDE/app/resources/GDJS/Runtime/force.js', './newIDE/app/resources/GDJS/Runtime/layer.js', + './newIDE/app/resources/GDJS/Runtime/RuntimeSceneLayer.js', './newIDE/app/resources/GDJS/Runtime/timer.js', './newIDE/app/resources/GDJS/Runtime/inputmanager.js', './newIDE/app/resources/GDJS/Runtime/runtimegame.js', @@ -62,6 +64,8 @@ module.exports = function (config) { './newIDE/app/resources/GDJS/Runtime/oncetriggers.js', './newIDE/app/resources/GDJS/Runtime/runtimebehavior.js', './newIDE/app/resources/GDJS/Runtime/spriteruntimeobject.js', + './newIDE/app/resources/GDJS/Runtime/CustomRuntimeObject.js', + './newIDE/app/resources/GDJS/Runtime/CustomRuntimeObjectInstanceContainer.js', './newIDE/app/resources/GDJS/Runtime/events-tools/commontools.js', './newIDE/app/resources/GDJS/Runtime/events-tools/runtimescenetools.js', './newIDE/app/resources/GDJS/Runtime/events-tools/inputtools.js', diff --git a/GDJS/tests/tests-utils/init.pixiruntimegamewithassets.js b/GDJS/tests/tests-utils/init.pixiruntimegamewithassets.js index b9edd5fa45..52755a105f 100644 --- a/GDJS/tests/tests-utils/init.pixiruntimegamewithassets.js +++ b/GDJS/tests/tests-utils/init.pixiruntimegamewithassets.js @@ -6,7 +6,7 @@ * @internal * @returns {Promise} A promise resolving with the game with loaded assets. */ -gdjs.getPixiRuntimeGameWithAssets = () => { +gdjs.getPixiRuntimeGameWithAssets = (customOptions = {}) => { if (gdjs.getPixiRuntimeGameWithAssets._pixiRuntimeGameWithAssetsPromise) { return gdjs.getPixiRuntimeGameWithAssets._pixiRuntimeGameWithAssetsPromise; } @@ -71,6 +71,28 @@ gdjs.getPixiRuntimeGameWithAssets = () => { }, ], }, + // Used in CustomRuntimeObjects.js + eventsFunctionsExtensions: [ + { + name: 'MyExtension', + eventsBasedObjects: [ + { + name: 'MyEventsBasedObject', + objects: [ + { + name: 'MySprite', + type: 'Sprite', + updateIfNotVisible: false, + variables: [], + behaviors: [], + animations: [], + effects: [], + }, + ], + }, + ], + }, + ], }); gdjs.getPixiRuntimeGameWithAssets._pixiRuntimeGameWithAssetsPromise = new Promise( diff --git a/GDJS/tests/tests/CustomRuntimeObject.js b/GDJS/tests/tests/CustomRuntimeObject.js new file mode 100644 index 0000000000..b13b703fd7 --- /dev/null +++ b/GDJS/tests/tests/CustomRuntimeObject.js @@ -0,0 +1,722 @@ +// @ts-check + +/** + * Basic tests for gdjs.SpriteRuntimeObject + */ +describe('gdjs.CustomRuntimeObject', function () { + /** + * Create a CustomRuntimeObject with a SpriteRuntimeObject using a 64x64 + * image with a custom collision mask. + * @param {gdjs.RuntimeInstanceContainer} instanceContainer + */ + const createCustomObject = (instanceContainer) => { + // The corresponding event-based object declaration is done by + // getPixiRuntimeGameWithAssets. + const customObject = new gdjs.CustomRuntimeObject(instanceContainer, { + name: 'MyCustomObject', + type: 'MyExtension::MyEventsBasedObject', + variables: [], + behaviors: [], + effects: [], + content: {}, + childrenContent: { + MySprite: { + updateIfNotVisible: false, + animations: [ + { + name: 'animation', + directions: [ + { + sprites: [ + { + image: 'base/tests-utils/assets/64x64.jpg', + originPoint: { name: 'Origin', x: 0, y: 0 }, + centerPoint: { + name: 'Center', + x: 32, + y: 32, + automatic: false, + }, + points: [ + { name: 'Center', x: 32, y: 32 }, + { name: 'Origin', x: 0, y: 0 }, + ], + hasCustomCollisionMask: true, + customCollisionMask: [ + [ + { x: 64, y: 64 }, + { x: 0, y: 64 }, + { x: 64, y: 0 }, + ], + ], + }, + ], + timeBetweenFrames: 0, + looping: false, + }, + ], + useMultipleDirections: false, + }, + ], + }, + }, + }); + instanceContainer.addObject(customObject); + return customObject; + }; + + /** + * @param {gdjs.RuntimeInstanceContainer} parent + */ + const createSpriteObject = (parent) => { + const sprite = parent.createObject('MySprite'); + if (!sprite) { + throw new Error("Object couldn't be created"); + } + return sprite; + }; + + const createSceneWithLayer = (runtimeGame) => { + const runtimeScene = new gdjs.RuntimeScene(runtimeGame); + runtimeScene.addLayer({ + name: '', + visibility: true, + cameras: [], + effects: [], + ambientLightColorR: 0, + ambientLightColorG: 0, + ambientLightColorB: 0, + isLightingLayer: false, + followBaseLayerCamera: false, + }); + return runtimeScene; + }; + + describe('with 2 sprites', function () { + /** @type {gdjs.CustomRuntimeObject} */ + let customObject; + /** @type {gdjs.RuntimeObject} */ + let leftSprite; + /** @type {gdjs.RuntimeObject} */ + let rightSprite; + + const makeCustomObjectWith2Children = (instanceContainer) => { + customObject = createCustomObject(instanceContainer); + + // Child-object creation should be done in the onCreate method of custom object. + // TODO EBO Rewrite this test when an events-based object has initialInstances. + leftSprite = createSpriteObject(customObject._instanceContainer); + rightSprite = createSpriteObject(customObject._instanceContainer); + + rightSprite.setX(64); + }; + + it('can return hit-boxes according to its children', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + + expect(leftSprite.getHitBoxes().length).to.be(1); + expect(leftSprite.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + + expect(rightSprite.getHitBoxes().length).to.be(1); + expect(rightSprite.getHitBoxes()[0].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + + expect(customObject.getWidth()).to.be(128); + expect(customObject.getHeight()).to.be(64); + + expect(customObject.getAABB()).to.eql({ + min: [0, 0], + max: [128, 64], + }); + + expect(customObject.getCenterXInScene()).to.be(64); + expect(customObject.getCenterYInScene()).to.be(32); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + }); + + it('can translate its hit-boxes', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + + customObject.setPosition(8, 16); + + expect(customObject.getWidth()).to.be(128); + expect(customObject.getHeight()).to.be(64); + + expect(customObject.getCenterXInScene()).to.be(64 + 8); + expect(customObject.getCenterYInScene()).to.be(32 + 16); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [72, 80], + [8, 80], + [72, 16], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [136, 80], + [72, 80], + [136, 16], + ]); + }); + + it('can rotate its hit-boxes', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + + customObject.setAngle(90); + + expect(customObject.getWidth()).to.be(128); + expect(customObject.getHeight()).to.be(64); + + expect(customObject.getCenterXInScene()).to.be(64); + expect(customObject.getCenterYInScene()).to.be(32); + + // sin(pi/2) can't be exactly 0 + // but cos(pi/2) is rounded to 1 because of the mantissa length. + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [32, 32], + [31.999999999999993, -31.999999999999996], + [96, 31.999999999999996], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [32, 96], + [32, 32], + [96, 96], + ]); + }); + + it('can scale its hit-boxes', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + + customObject.setWidth(32); + customObject.setHeight(96); + + expect(customObject.getWidth()).to.be(32); + expect(customObject.getHeight()).to.be(96); + + expect(customObject.getCenterXInScene()).to.be(16); + expect(customObject.getCenterYInScene()).to.be(48); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [16, 96], + [0, 96], + [16, 0], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [32, 96], + [16, 96], + [32, 0], + ]); + }); + + it('can translate, scale and rotate its hit-boxes', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + + customObject.setPosition(8, 16); + customObject.setAngle(90); + customObject.setWidth(32); + customObject.setHeight(96); + + expect(customObject.getWidth()).to.be(32); + expect(customObject.getHeight()).to.be(96); + + expect(customObject.getCenterXInScene()).to.be(32 / 2 + 8); + expect(customObject.getCenterYInScene()).to.be(96 / 2 + 16); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [-12, 100], + [-12, 84], + [84, 100], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [-12, 116], + [-12, 100], + [84, 116], + ]); + }); + + it('keeps hit-boxes up to date when its children move and push the bottom-right corner', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + + rightSprite.setPosition(64 + 32, 0 + 8); + + expect(customObject.getWidth()).to.be(128 + 32); + expect(customObject.getHeight()).to.be(64 + 8); + + expect(customObject.getX()).to.be(0); + expect(customObject.getY()).to.be(0); + + expect(customObject.getCenterXInScene()).to.be(64 + 16); + expect(customObject.getCenterYInScene()).to.be(32 + 4); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [160, 72], + [96, 72], + [160, 8], + ]); + }); + + it('keeps hit-boxes up to date when its children move and push the top-left corner', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + + leftSprite.setPosition(0 - 32, 0 - 8); + + expect(customObject.getWidth()).to.be(128 + 32); + expect(customObject.getHeight()).to.be(64 + 8); + + expect(customObject.getX()).to.be(0); + expect(customObject.getY()).to.be(0); + + expect(customObject.getDrawableX()).to.be(-32); + expect(customObject.getDrawableY()).to.be(-8); + + expect(customObject.getCenterXInScene()).to.be(64 - 16); + expect(customObject.getCenterYInScene()).to.be(32 - 4); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [32, 56], + [-32, 56], + [32, -8], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + }); + + it('keeps hit-boxes up to date when its children move and shrink the top-left corner', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + + leftSprite.setPosition(0 + 32, 0 + 8); + rightSprite.setPosition(64, 0 + 8); + + expect(customObject.getWidth()).to.be(128 - 32); + expect(customObject.getHeight()).to.be(64); + + expect(customObject.getX()).to.be(0); + expect(customObject.getY()).to.be(0); + + expect(customObject.getDrawableX()).to.be(32); + expect(customObject.getDrawableY()).to.be(8); + + expect(customObject.getCenterXInScene()).to.be(64 + 16); + expect(customObject.getCenterYInScene()).to.be(32 + 8); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [96, 72], + [32, 72], + [96, 8], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [128, 72], + [64, 72], + [128, 8], + ]); + }); + + it('keeps hit-boxes up to date when new children is added', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + + const middleSprite = createSpriteObject(customObject._instanceContainer); + middleSprite.setX(32); + + expect(customObject.getHitBoxes().length).to.be(3); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + expect(customObject.getHitBoxes()[2].vertices).to.eql([ + [96, 64], + [32, 64], + [96, 0], + ]); + }); + + it('properly computes hitboxes and point positions after the scene layer camera has moved', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + const sceneLayer = runtimeScene.getLayer(''); + + // Check the hitboxes and positions with default camera position + expect(customObject.getCenterXInScene()).to.be(64); + expect(customObject.getCenterYInScene()).to.be(32); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + + // Move the layer camera. + sceneLayer.setCameraX(2000); + sceneLayer.setCameraY(4000); + customObject.invalidateHitboxes(); + + // The object hitboxes and positions stay the same. + expect(customObject.getCenterXInScene()).to.be(64); + expect(customObject.getCenterYInScene()).to.be(32); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + }); + + it('properly computes hitboxes and point positions after the custom object layer camera has moved', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + const customObjectLayer = customObject + .getInstanceContainer() + .getLayer(''); + + // Check the hitboxes and positions with default camera position + expect(customObject.getCenterXInScene()).to.be(64); + expect(customObject.getCenterYInScene()).to.be(32); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + + // Move the layer camera. + customObjectLayer.setCameraX(2000); + customObjectLayer.setCameraY(4000); + customObject.invalidateHitboxes(); + + // The object hitboxes and positions stay the same. + expect(customObject.getCenterXInScene()).to.be(64); + expect(customObject.getCenterYInScene()).to.be(32); + + expect(customObject.getHitBoxes().length).to.be(2); + expect(customObject.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + expect(customObject.getHitBoxes()[1].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + }); + + /** @type {FloatPoint} */ + const workingPoint = [0, 0]; + + describe('convertCoords', function () { + it('can transform a point from the scene', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + const instanceContainer = customObject._instanceContainer; + + customObject.setPosition(16, 8); + expect(instanceContainer.convertCoords(16, 8, workingPoint)).to.eql([ + 0, + 0, + ]); + }); + + it('can transform a point from the scene with a negative AABB min position', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + const instanceContainer = customObject._instanceContainer; + + leftSprite.setPosition(-16, -8); + customObject.setPosition(0, 0); + expect(instanceContainer.convertCoords(0, 0, workingPoint)).to.eql([ + 0, + 0, + ]); + }); + + it('can transform a point from the scene with a positive AABB min position', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + const instanceContainer = customObject._instanceContainer; + + leftSprite.setPosition(16, 8); + customObject.setPosition(0, 0); + expect(instanceContainer.convertCoords(0, 0, workingPoint)).to.eql([ + 0, + 0, + ]); + }); + }); + + describe('convertInverseCoords', function () { + it('can transform a point to the scene', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + const instanceContainer = customObject._instanceContainer; + + customObject.setPosition(16, 8); + expect( + instanceContainer.convertInverseCoords(0, 0, workingPoint) + ).to.eql([16, 8]); + }); + + it('can transform a point to scene with a negative AABB min position', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + const instanceContainer = customObject._instanceContainer; + + leftSprite.setPosition(-16, -8); + customObject.setPosition(0, 0); + expect( + instanceContainer.convertInverseCoords(0, 0, workingPoint) + ).to.eql([0, 0]); + }); + + it('can transform a point to the scene with a positive AABB min position', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2Children(runtimeScene); + const instanceContainer = customObject._instanceContainer; + + leftSprite.setPosition(16, 8); + customObject.setPosition(0, 0); + expect( + instanceContainer.convertInverseCoords(0, 0, workingPoint) + ).to.eql([0, 0]); + }); + }); + }); + + describe('with custom objects as children', function () { + /** @type {gdjs.CustomRuntimeObject} */ + let rootCustomObject; + /** @type {gdjs.CustomRuntimeObject} */ + let topCustomObject; + /** @type {gdjs.RuntimeObject} */ + let topLeftSprite; + /** @type {gdjs.RuntimeObject} */ + let topRightSprite; + /** @type {gdjs.CustomRuntimeObject} */ + let bottomCustomObject; + /** @type {gdjs.RuntimeObject} */ + let bottomLeftSprite; + /** @type {gdjs.RuntimeObject} */ + let bottomRightSprite; + + const makeCustomObjectWith2ChildrenAt2Levels = (instanceContainer) => { + rootCustomObject = createCustomObject(instanceContainer); + topCustomObject = createCustomObject(rootCustomObject._instanceContainer); + // Child-object creation should be done in the onCreate method of custom object. + // TODO EBO Rewrite this test when an events-based object has initialInstances. + topLeftSprite = createSpriteObject(topCustomObject._instanceContainer); + topRightSprite = createSpriteObject(topCustomObject._instanceContainer); + topRightSprite.setX(64); + + bottomCustomObject = createCustomObject( + rootCustomObject._instanceContainer + ); + // Child-object creation should be done in the onCreate method of custom object. + // TODO EBO Rewrite this test when an events-based object has initialInstances. + bottomLeftSprite = createSpriteObject( + bottomCustomObject._instanceContainer + ); + bottomRightSprite = createSpriteObject( + bottomCustomObject._instanceContainer + ); + bottomRightSprite.setX(64); + + bottomCustomObject.setY(64); + }; + + it('can return hit-boxes according to its children', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2ChildrenAt2Levels(runtimeScene); + + expect(rootCustomObject.getWidth()).to.be(128); + expect(rootCustomObject.getHeight()).to.be(128); + + expect(rootCustomObject.getAABB()).to.eql({ + min: [0, 0], + max: [128, 128], + }); + + expect(rootCustomObject.getCenterXInScene()).to.be(64); + expect(rootCustomObject.getCenterYInScene()).to.be(64); + + expect(rootCustomObject.getHitBoxes().length).to.be(4); + expect(rootCustomObject.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + expect(rootCustomObject.getHitBoxes()[1].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + expect(rootCustomObject.getHitBoxes()[2].vertices).to.eql([ + [64, 128], + [0, 128], + [64, 64], + ]); + expect(rootCustomObject.getHitBoxes()[3].vertices).to.eql([ + [128, 128], + [64, 128], + [128, 64], + ]); + }); + + it('keeps hit-boxes up to date when its children move and push the bottom-right corner', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2ChildrenAt2Levels(runtimeScene); + + bottomRightSprite.setPosition(64 + 32, 0 + 8); + + expect(rootCustomObject.getWidth()).to.be(128 + 32); + expect(rootCustomObject.getHeight()).to.be(128 + 8); + + expect(rootCustomObject.getX()).to.be(0); + expect(rootCustomObject.getY()).to.be(0); + + expect(rootCustomObject.getCenterXInScene()).to.be(64 + 16); + expect(rootCustomObject.getCenterYInScene()).to.be(64 + 4); + + expect(rootCustomObject.getHitBoxes().length).to.be(4); + expect(rootCustomObject.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + expect(rootCustomObject.getHitBoxes()[1].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + expect(rootCustomObject.getHitBoxes()[2].vertices).to.eql([ + [64, 128], + [0, 128], + [64, 64], + ]); + expect(rootCustomObject.getHitBoxes()[3].vertices).to.eql([ + [160, 136], + [96, 136], + [160, 72], + ]); + }); + + it('keeps hit-boxes up to date when new children is added', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); + const runtimeScene = createSceneWithLayer(runtimeGame); + makeCustomObjectWith2ChildrenAt2Levels(runtimeScene); + + const topMiddleSprite = createSpriteObject( + topCustomObject._instanceContainer + ); + topMiddleSprite.setX(32); + + expect(rootCustomObject.getHitBoxes().length).to.be(5); + expect(rootCustomObject.getHitBoxes()[0].vertices).to.eql([ + [64, 64], + [0, 64], + [64, 0], + ]); + expect(rootCustomObject.getHitBoxes()[1].vertices).to.eql([ + [128, 64], + [64, 64], + [128, 0], + ]); + // This is the new child. + expect(rootCustomObject.getHitBoxes()[2].vertices).to.eql([ + [96, 64], + [32, 64], + [96, 0], + ]); + expect(rootCustomObject.getHitBoxes()[3].vertices).to.eql([ + [64, 128], + [0, 128], + [64, 64], + ]); + expect(rootCustomObject.getHitBoxes()[4].vertices).to.eql([ + [128, 128], + [64, 128], + [128, 64], + ]); + }); + }); +}); diff --git a/GDJS/tests/tests/Extensions/testruntimeobject.js b/GDJS/tests/tests/Extensions/testruntimeobject.js index 027e802900..af7aae6deb 100644 --- a/GDJS/tests/tests/Extensions/testruntimeobject.js +++ b/GDJS/tests/tests/Extensions/testruntimeobject.js @@ -18,7 +18,7 @@ gdjs.TestRuntimeObject = class TestRuntimeObject extends gdjs.RuntimeObject { /** * @param {gdjs.RuntimeScene} runtimeScene - * @param {ObjectData} objectData + * @param {ObjectData & any} objectData */ constructor(runtimeScene, objectData) { // *ALWAYS* call the base gdjs.RuntimeObject constructor. @@ -31,13 +31,13 @@ gdjs.TestRuntimeObject = class TestRuntimeObject extends gdjs.RuntimeObject { setCustomWidthAndHeight(customWidth, customHeight) { this._customWidth = customWidth; this._customHeight = customHeight; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } setCustomCenter(customCenterX, customCenterY) { this._customCenterX = customCenterX; this._customCenterY = customCenterY; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } getRendererObject() { @@ -51,7 +51,7 @@ gdjs.TestRuntimeObject = class TestRuntimeObject extends gdjs.RuntimeObject { setWidth(width) { if (width !== this._customWidth) { this._customWidth = width; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } return this._customWidth; } @@ -63,7 +63,7 @@ gdjs.TestRuntimeObject = class TestRuntimeObject extends gdjs.RuntimeObject { setHeight(height) { if (height !== this._customHeight) { this._customHeight = height; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } return this._customHeight; } diff --git a/GDJS/tests/tests/Extensions/testspriteruntimeobject.js b/GDJS/tests/tests/Extensions/testspriteruntimeobject.js index b6e6b6adf1..4376a38140 100644 --- a/GDJS/tests/tests/Extensions/testspriteruntimeobject.js +++ b/GDJS/tests/tests/Extensions/testspriteruntimeobject.js @@ -33,7 +33,7 @@ this._customHeight = customHeight; super._scaleX = this._customWidth / this._unscaledWidth; super._scaleY = this._customHeight / this._unscaledHeight; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } setUnscaledWidthAndHeight(unscaledWidth, unscaledHeight) { @@ -41,13 +41,13 @@ this._unscaledHeight = unscaledHeight; super._scaleX = this._customWidth / this._unscaledWidth; super._scaleY = this._customHeight / this._unscaledHeight; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } setCustomCenter(customCenterX, customCenterY) { this._customCenterX = customCenterX; this._customCenterY = customCenterY; - this.hitBoxesDirty = true; + this.invalidateHitboxes(); } getRendererObject() { diff --git a/GDJS/tests/tests/layer.js b/GDJS/tests/tests/layer.js index 2e3a383a62..17fa018d30 100644 --- a/GDJS/tests/tests/layer.js +++ b/GDJS/tests/tests/layer.js @@ -9,7 +9,7 @@ describe('gdjs.Layer', function() { var runtimeScene = new gdjs.RuntimeScene(runtimeGame); it('can convert coordinates', function(){ - var layer = new gdjs.Layer({name: 'My layer', visibility: true, effects:[]}, runtimeScene) + var layer = new gdjs.RuntimeSceneLayer({name: 'My layer', visibility: true, effects:[]}, runtimeScene) layer.setCameraX(100, 0); layer.setCameraY(200, 0); layer.setCameraRotation(90, 0); @@ -18,7 +18,7 @@ describe('gdjs.Layer', function() { expect(layer.convertCoords(350, 450, 0)[1]).to.be.within(149.9999, 150.001); }); it('can convert inverse coordinates', function(){ - var layer = new gdjs.Layer({name: 'My layer', visibility: true, effects:[]}, runtimeScene) + var layer = new gdjs.RuntimeSceneLayer({name: 'My layer', visibility: true, effects:[]}, runtimeScene) layer.setCameraX(100, 0); layer.setCameraY(200, 0); layer.setCameraRotation(90, 0); diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index aef57093c1..b7ca4b2952 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -2932,7 +2932,14 @@ interface BehaviorCodeGenerator { [Const, Value] DOMString GenerateRuntimeBehaviorCompleteCode([Const] DOMString extensionName, [Const, Ref] EventsBasedBehavior eventsBasedBehavior, [Const] DOMString codeNamespace, [Const, Ref] MapStringString behaviorMethodMangledNames, [Ref] SetString includes, boolean compilationForRuntime); [Const, Value] DOMString STATIC_GetBehaviorPropertyGetterName([Const] DOMString propertyName); [Const, Value] DOMString STATIC_GetBehaviorPropertySetterName([Const] DOMString propertyName); +}; +[Prefix="gdjs::"] +interface ObjectCodeGenerator { + void ObjectCodeGenerator([Ref] Project project); + [Const, Value] DOMString GenerateRuntimeObjectCompleteCode([Const] DOMString extensionName, [Const, Ref] EventsBasedObject eventsBasedObject, [Const] DOMString codeNamespace, [Const, Ref] MapStringString objectMethodMangledNames, [Ref] SetString includes, boolean compilationForRuntime); + [Const, Value] DOMString STATIC_GetObjectPropertyGetterName([Const] DOMString propertyName); + [Const, Value] DOMString STATIC_GetObjectPropertySetterName([Const] DOMString propertyName); }; [Prefix="gdjs::"] diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index b6d3456d9d..1ccdd057fd 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -73,6 +73,7 @@ #include #include #include +#include #include #include #include @@ -628,6 +629,8 @@ typedef ExtensionAndMetadata ExtensionAndExpressionMetadata; #define STATIC_GetBehaviorPropertyGetterName GetBehaviorPropertyGetterName #define STATIC_GetBehaviorPropertySetterName GetBehaviorPropertySetterName +#define STATIC_GetObjectPropertyGetterName GetObjectPropertyGetterName +#define STATIC_GetObjectPropertySetterName GetObjectPropertySetterName #define STATIC_GetPropertyActionName GetPropertyActionName #define STATIC_GetPropertyConditionName GetPropertyConditionName #define STATIC_GetPropertyExpressionName GetPropertyExpressionName diff --git a/GDevelop.js/scripts/generate-types.js b/GDevelop.js/scripts/generate-types.js index 8614fb516b..929ec4dade 100644 --- a/GDevelop.js/scripts/generate-types.js +++ b/GDevelop.js/scripts/generate-types.js @@ -365,6 +365,12 @@ type ParticleEmitterObject_RendererType = 0 | 1 | 2` 'type: string, description: string, optionalObjectType?: string, parameterIsOptional?: boolean', 'types/gdexpressionmetadata.js' ); + shell.sed( + '-i', + 'type: string, description: string, optionalObjectType: string, parameterIsOptional: boolean', + 'type: string, description: string, optionalObjectType?: string, parameterIsOptional?: boolean', + 'types/gdmultipleinstructionmetadata.js' + ); // Add a notice that the file is auto-generated. shell.sed( diff --git a/GDevelop.js/types/gdmultipleinstructionmetadata.js b/GDevelop.js/types/gdmultipleinstructionmetadata.js index 82e1eab77e..5e44ecf524 100644 --- a/GDevelop.js/types/gdmultipleinstructionmetadata.js +++ b/GDevelop.js/types/gdmultipleinstructionmetadata.js @@ -1,6 +1,6 @@ // Automatically generated by GDevelop.js/scripts/generate-types.js declare class gdMultipleInstructionMetadata { - addParameter(type: string, description: string, optionalObjectType: string, parameterIsOptional: boolean): gdMultipleInstructionMetadata; + addParameter(type: string, description: string, optionalObjectType?: string, parameterIsOptional?: boolean): gdMultipleInstructionMetadata; addCodeOnlyParameter(type: string, supplementaryInformation: string): gdMultipleInstructionMetadata; setDefaultValue(defaultValue: string): gdMultipleInstructionMetadata; setParameterLongDescription(longDescription: string): gdMultipleInstructionMetadata; diff --git a/GDevelop.js/types/gdobjectcodegenerator.js b/GDevelop.js/types/gdobjectcodegenerator.js new file mode 100644 index 0000000000..db67db9b44 --- /dev/null +++ b/GDevelop.js/types/gdobjectcodegenerator.js @@ -0,0 +1,9 @@ +// Automatically generated by GDevelop.js/scripts/generate-types.js +declare class gdObjectCodeGenerator { + constructor(project: gdProject): void; + generateRuntimeObjectCompleteCode(extensionName: string, eventsBasedObject: gdEventsBasedObject, codeNamespace: string, objectMethodMangledNames: gdMapStringString, includes: gdSetString, compilationForRuntime: boolean): string; + static getObjectPropertyGetterName(propertyName: string): string; + static getObjectPropertySetterName(propertyName: string): string; + delete(): void; + ptr: number; +}; \ No newline at end of file diff --git a/GDevelop.js/types/libgdevelop.js b/GDevelop.js/types/libgdevelop.js index ecf51dbd8e..5ec256f337 100644 --- a/GDevelop.js/types/libgdevelop.js +++ b/GDevelop.js/types/libgdevelop.js @@ -212,6 +212,7 @@ declare class libGDevelop { ParticleEmitterObject: Class; LayoutCodeGenerator: Class; BehaviorCodeGenerator: Class; + ObjectCodeGenerator: Class; EventsFunctionsExtensionCodeGenerator: Class; PreviewExportOptions: Class; Exporter: Class; diff --git a/newIDE/app/public/res/functions/reload.svg b/newIDE/app/public/res/functions/reload.svg new file mode 100644 index 0000000000..2e566ad32a --- /dev/null +++ b/newIDE/app/public/res/functions/reload.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/ObjectMethodSelectorDialog.js b/newIDE/app/src/EventsFunctionsExtensionEditor/ObjectMethodSelectorDialog.js index 0b16bb4cd0..f34a0b991d 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/ObjectMethodSelectorDialog.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/ObjectMethodSelectorDialog.js @@ -9,6 +9,7 @@ import { List, ListItem } from '../UI/List'; import Dialog from '../UI/Dialog'; import HelpButton from '../UI/HelpButton'; import Create from '../UI/CustomSvgIcons/Behaviors/Create'; +import Reload from '../UI/CustomSvgIcons/Behaviors/Reload'; import Step from '../UI/CustomSvgIcons/Behaviors/Step'; import Destroy from '../UI/CustomSvgIcons/Behaviors/Destroy'; import Action from '../UI/CustomSvgIcons/Behaviors/Action'; @@ -156,14 +157,13 @@ export default function ObjectMethodSelectorDialog({ /> } - name={'doStepPreEvents'} - disabled={eventsFunctions.hasEventsFunctionNamed('doStepPreEvents')} + name={'doStepPostEvents'} + disabled={eventsFunctions.hasEventsFunctionNamed('doStepPostEvents')} onChoose={onChoose} description={ Events that will be run at every frame (roughly 60 times per - second), for every object, before the events from the events sheet - are launched. + second), for every object, after the events from the events sheet. } /> @@ -188,17 +188,16 @@ export default function ObjectMethodSelectorDialog({ Other lifecycle methods } - name={'doStepPostEvents'} + icon={} + name={'onHotReloading'} disabled={eventsFunctions.hasEventsFunctionNamed( - 'doStepPostEvents' + 'onHotReloading' )} onChoose={onChoose} description={ - Events that will be run at every frame (roughly 60 times per - second), for every object, after the events from the events - sheet. + Events that will be run when the preview is being + hot-reloaded. } /> diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/index.js b/newIDE/app/src/EventsFunctionsExtensionEditor/index.js index 42582b77ef..7de32be933 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/index.js @@ -465,7 +465,11 @@ export default class EventsFunctionsExtensionEditor extends React.Component< this.state.selectedEventsFunction && gd.compare(eventsFunction, this.state.selectedEventsFunction) ) { - this._selectEventsFunction(null, this.state.selectedEventsBasedBehavior); + this._selectEventsFunction( + null, + this.state.selectedEventsBasedBehavior, + this.state.selectedEventsBasedObject + ); } cb(true); diff --git a/newIDE/app/src/EventsFunctionsExtensionsLoader/CodeWriters/BrowserS3EventsFunctionCodeWriter.js b/newIDE/app/src/EventsFunctionsExtensionsLoader/CodeWriters/BrowserS3EventsFunctionCodeWriter.js index 2c03ca51c1..959226d3f7 100644 --- a/newIDE/app/src/EventsFunctionsExtensionsLoader/CodeWriters/BrowserS3EventsFunctionCodeWriter.js +++ b/newIDE/app/src/EventsFunctionsExtensionsLoader/CodeWriters/BrowserS3EventsFunctionCodeWriter.js @@ -48,5 +48,18 @@ export const makeBrowserS3EventsFunctionCodeWriter = ({ ContentType: 'text/javascript', }); }, + writeObjectCode: ( + objectCodeNamespace: string, + code: string + ): Promise => { + const key = getPathFor(objectCodeNamespace); + onWriteFile({ includeFile: key, content: code }); + console.log(`Uploading object generated code to ${key}...`); + return uploadObject({ + Key: getPathFor(objectCodeNamespace), + Body: code, + ContentType: 'text/javascript', + }); + }, }; }; diff --git a/newIDE/app/src/EventsFunctionsExtensionsLoader/CodeWriters/LocalEventsFunctionCodeWriter.js b/newIDE/app/src/EventsFunctionsExtensionsLoader/CodeWriters/LocalEventsFunctionCodeWriter.js index d2c5158def..01e78e686c 100644 --- a/newIDE/app/src/EventsFunctionsExtensionsLoader/CodeWriters/LocalEventsFunctionCodeWriter.js +++ b/newIDE/app/src/EventsFunctionsExtensionsLoader/CodeWriters/LocalEventsFunctionCodeWriter.js @@ -63,6 +63,20 @@ export const makeLocalEventsFunctionCodeWriter = ({ fs.writeFile(includeFile, code, err => { if (err) return reject(err); + resolve(); + }); + }); + }, + writeObjectCode: ( + objectCodeNamespace: string, + code: string + ): Promise => { + return new Promise((resolve, reject) => { + const includeFile = getPathFor(objectCodeNamespace); + onWriteFile({ includeFile, content: code }); + fs.writeFile(includeFile, code, err => { + if (err) return reject(err); + resolve(); }); }); diff --git a/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.js b/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.js index 1f9e86573e..7c0f4c3ead 100644 --- a/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.js +++ b/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.js @@ -82,15 +82,181 @@ export const declareBehaviorMetadata = ( * events based object. */ export const declareObjectMetadata = ( + i18n: I18nType, extension: gdPlatformExtension, eventsBasedObject: gdEventsBasedObject ): gdObjectMetadata => { - return extension.addEventsBasedObject( + const objectMetadata = extension.addEventsBasedObject( eventsBasedObject.getName(), eventsBasedObject.getFullName() || eventsBasedObject.getName(), eventsBasedObject.getDescription(), getExtensionIconUrl(extension) ); + + // TODO EBO Use full type to identify object to avoid collision. + // Objects are identified by their name alone. + const objectType = eventsBasedObject.getName(); + + objectMetadata + .addAction( + 'Width', + i18n._('Width'), + i18n._('Change the width of an object.'), + i18n._('the width'), + i18n._('Size'), + 'res/actions/scaleWidth24_black.png', + 'res/actions/scale_black.png' + ) + .addParameter('object', i18n._('Object'), objectType) + .useStandardOperatorParameters('number') + .markAsAdvanced() + .getCodeExtraInformation() + .setFunctionName('setWidth') + .setGetter('getWidth'); + + objectMetadata + .addAction( + 'Height', + i18n._('Height'), + i18n._('Change the height of an object.'), + i18n._('the height'), + i18n._('Size'), + 'res/actions/scaleHeight24_black.png', + 'res/actions/scale_black.png' + ) + .addParameter('object', i18n._('Object'), objectType) + .useStandardOperatorParameters('number') + .markAsAdvanced() + .getCodeExtraInformation() + .setFunctionName('setHeight') + .setGetter('getHeight'); + + objectMetadata + .addAction( + 'Scale', + i18n._('Scale'), + i18n._('Modify the scale of the specified object.'), + i18n._('the scale'), + i18n._('Size'), + 'res/actions/scale24_black.png', + 'res/actions/scale_black.png' + ) + .addParameter('object', i18n._('Object'), objectType) + .useStandardOperatorParameters('number') + .markAsAdvanced() + .getCodeExtraInformation() + .setFunctionName('setScale') + .setGetter('getScale'); + + objectMetadata + .addExpressionAndConditionAndAction( + 'number', + 'ScaleX', + i18n._('Scale on X axis'), + i18n._("the width's scale of an object"), + i18n._("the width's scale"), + i18n._('Size'), + 'res/actions/scaleWidth24_black.png' + ) + .addParameter('object', i18n._('Object'), objectType) + .useStandardParameters('number') + .markAsAdvanced() + .setFunctionName('setScaleX') + .setGetter('getScaleY'); + + objectMetadata + .addExpressionAndConditionAndAction( + 'number', + 'ScaleY', + i18n._('Scale on Y axis'), + i18n._("the height's scale of an object"), + i18n._("the height's scale"), + i18n._('Size'), + 'res/actions/scaleHeight24_black.png' + ) + .addParameter('object', i18n._('Object'), objectType) + .useStandardParameters('number') + .markAsAdvanced() + .setFunctionName('setScaleY') + .setGetter('getScaleY'); + + objectMetadata + .addAction( + 'FlipX', + i18n._('Flip the object horizontally'), + i18n._('Flip the object horizontally'), + i18n._('Flip horizontally _PARAM0_: _PARAM1_'), + i18n._('Effects'), + 'res/actions/flipX24_black.png', + 'res/actions/flipX_black.png' + ) + .addParameter('object', i18n._('Object'), objectType) + .addParameter('yesorno', i18n._('Activate flipping')) + .markAsSimple() + .getCodeExtraInformation() + .setFunctionName('flipX'); + + objectMetadata + .addAction( + 'FlipY', + i18n._('Flip the object vertically'), + i18n._('Flip the object vertically'), + i18n._('Flip vertically _PARAM0_: _PARAM1_'), + i18n._('Effects'), + 'res/actions/flipY24_black.png', + 'res/actions/flipY_black.png' + ) + .addParameter('object', i18n._('Object'), objectType) + .addParameter('yesorno', i18n._('Activate flipping')) + .markAsSimple() + .getCodeExtraInformation() + .setFunctionName('flipY'); + + objectMetadata + .addCondition( + 'FlippedX', + i18n._('Horizontally flipped'), + i18n._('Check if the object is horizontally flipped'), + i18n._('_PARAM0_ is horizontally flipped'), + i18n._('Effects'), + 'res/actions/flipX24_black.png', + 'res/actions/flipX_black.png' + ) + .addParameter('object', i18n._('Object'), objectType) + .getCodeExtraInformation() + .setFunctionName('isFlippedX'); + + objectMetadata + .addCondition( + 'FlippedY', + i18n._('Vertically flipped'), + i18n._('Check if the object is vertically flipped'), + i18n._('_PARAM0_ is vertically flipped'), + i18n._('Effects'), + 'res/actions/flipY24_black.png', + 'res/actions/flipY_black.png' + ) + .addParameter('object', i18n._('Object'), objectType) + .getCodeExtraInformation() + .setFunctionName('isFlippedY'); + + objectMetadata + .addExpressionAndConditionAndAction( + 'number', + 'Opacity', + i18n._('Opacity'), + i18n._( + 'the opacity of an object, between 0 (fully transparent) to 255 (opaque)' + ), + i18n._('the opacity'), + i18n._('Visibility'), + 'res/conditions/opacity24.png' + ) + .addParameter('object', i18n._('Object'), objectType) + .useStandardParameters('number') + .setFunctionName('getOpacity'); + + return objectMetadata; }; /** @@ -118,9 +284,8 @@ export const isBehaviorLifecycleEventsFunction = (functionName: string) => { * that will be called automatically by the game engine. */ export const isObjectLifecycleEventsFunction = (functionName: string) => { - // TODO EBO Rename doStepPreEvents and doStepPostEvents return ( - ['onCreated', 'doStepPreEvents', 'doStepPostEvents', 'onDestroy'].indexOf( + ['onCreated', 'doStepPostEvents', 'onDestroy', 'onHotReloading'].indexOf( functionName ) !== -1 ); @@ -569,11 +734,10 @@ export const declareObjectPropertiesInstructionAndExpressions = ( mapVector(eventsBasedObject.getPropertyDescriptors(), property => { const propertyType = property.getType(); const propertyName = property.getName(); - // TODO EBO Use the proper methods for objects when the generator is implemented. - const getterName = gd.BehaviorCodeGenerator.getBehaviorPropertyGetterName( + const getterName = gd.ObjectCodeGenerator.getObjectPropertyGetterName( propertyName ); - const setterName = gd.BehaviorCodeGenerator.getBehaviorPropertySetterName( + const setterName = gd.ObjectCodeGenerator.getObjectPropertySetterName( propertyName ); const propertyLabel = diff --git a/newIDE/app/src/EventsFunctionsExtensionsLoader/index.js b/newIDE/app/src/EventsFunctionsExtensionsLoader/index.js index 9e4db30d98..6e00436f3e 100644 --- a/newIDE/app/src/EventsFunctionsExtensionsLoader/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionsLoader/index.js @@ -23,6 +23,7 @@ export type EventsFunctionCodeWriter = {| getIncludeFileFor: (functionName: string) => string, writeFunctionCode: (functionName: string, code: string) => Promise, writeBehaviorCode: (behaviorName: string, code: string) => Promise, + writeObjectCode: (objectName: string, code: string) => Promise, |}; export type IncludeFileContent = {| @@ -516,13 +517,18 @@ function generateObject( options: Options, codeGenerationContext: CodeGenerationContext ): Promise { - const objectMetadata = declareObjectMetadata(extension, eventsBasedObject); + const objectMetadata = declareObjectMetadata( + options.i18n, + extension, + eventsBasedObject + ); const eventsFunctionsContainer = eventsBasedObject.getEventsFunctions(); const codeNamespace = getObjectFunctionCodeNamespace( eventsBasedObject, codeGenerationContext.codeNamespacePrefix ); + // TODO EBO Handle name collision between objects and behaviors. const includeFile = options.eventsFunctionCodeWriter.getIncludeFileFor( codeNamespace ); @@ -579,9 +585,39 @@ function generateObject( // Generate code for the object and its methods if (!options.skipCodeGeneration) { - // TODO EBO Generate the code. + const includeFiles = new gd.SetString(); + const objectCodeGenerator = new gd.ObjectCodeGenerator(project); + const code = objectCodeGenerator.generateRuntimeObjectCompleteCode( + eventsFunctionsExtension.getName(), + eventsBasedObject, + codeNamespace, + objectMethodMangledNames, + includeFiles, + + // For now, always generate functions for runtime (this disables + // generation of profiling for groups (see EventsCodeGenerator)) + // as extensions generated can be used either for preview or export. + true + ); + objectCodeGenerator.delete(); objectMethodMangledNames.delete(); - return Promise.resolve(); + + // Add any include file required by the functions to the list + // of include files for this object (so that when used, the "dependencies" + // are transitively included). + includeFiles + .toNewVectorString() + .toJSArray() + .forEach((includeFile: string) => { + objectMetadata.addIncludeFile(includeFile); + }); + + includeFiles.delete(); + + return options.eventsFunctionCodeWriter.writeObjectCode( + codeNamespace, + code + ); } else { // Skip code generation objectMethodMangledNames.delete(); diff --git a/newIDE/app/src/EventsFunctionsList/index.js b/newIDE/app/src/EventsFunctionsList/index.js index 8151078139..9c62187d99 100644 --- a/newIDE/app/src/EventsFunctionsList/index.js +++ b/newIDE/app/src/EventsFunctionsList/index.js @@ -171,6 +171,9 @@ export default class EventsFunctionsList extends React.Component { case 'onFirstSceneLoaded': case 'onCreated': return 'res/functions/create.svg'; + + case 'onHotReloading': + return 'res/functions/reload.svg'; } case gd.EventsFunction.Condition: return 'res/functions/condition.svg'; diff --git a/newIDE/app/src/UI/CustomSvgIcons/Behaviors/Reload.js b/newIDE/app/src/UI/CustomSvgIcons/Behaviors/Reload.js new file mode 100644 index 0000000000..2cadbea731 --- /dev/null +++ b/newIDE/app/src/UI/CustomSvgIcons/Behaviors/Reload.js @@ -0,0 +1,20 @@ +import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; + +// eslint-disable-next-line import/no-anonymous-default-export +export default props => ( + + + + + +); diff --git a/newIDE/app/src/Utils/GDevelopServices/Asset.js b/newIDE/app/src/Utils/GDevelopServices/Asset.js index 66bf1985e9..f2c703f9c8 100644 --- a/newIDE/app/src/Utils/GDevelopServices/Asset.js +++ b/newIDE/app/src/Utils/GDevelopServices/Asset.js @@ -35,7 +35,7 @@ export type AssetCustomization = |}; export type ObjectAsset = {| - object: any /*(serialized gdObject)*/, + object: any /*(serialized gdObjectConfiguration)*/, resources: Array, customization: Array, |};