diff --git a/extensions/reviewed/Sticker.json b/extensions/reviewed/Sticker.json index c157e6dd..28358dc2 100644 --- a/extensions/reviewed/Sticker.json +++ b/extensions/reviewed/Sticker.json @@ -8,7 +8,7 @@ "name": "Sticker", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/sticker-outline.svg", "shortDescription": "Make objects follow the position and rotation of the object they are stuck to.", - "version": "0.4.0", + "version": "0.5.0", "description": [ "This extension can be useful to:", "* Stick accessories to moving objects", @@ -41,6 +41,232 @@ ], "dependencies": [], "eventsFunctions": [ + { + "description": "Define helper classes JavaScript code.", + "fullName": "Define helper classes", + "functionType": "Action", + "name": "DefineHelperClasses", + "private": true, + "sentence": "Define helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs._stickerExtension) {", + " return;", + "}", + "", + "// Unstick from deleted objects.", + "gdjs.registerObjectDeletedFromSceneCallback(function (runtimeScene, deletedObject) {", + " const extension = runtimeScene._stickerExtension;", + " if (!extension) {", + " return;", + " }", + " const allStickers = runtimeScene._stickerExtension.allStickers;", + " for (const behavior of allStickers) {", + " const sticker = behavior._sticker;", + " if (sticker.basisObject === deletedObject) {", + " if (behavior._getIsDestroyedWithParent()) {", + " behavior.owner.deleteFromScene(runtimeScene);", + " }", + " sticker.basisObject = null;", + " }", + " }", + "});", + "", + "class Sticker {", + " /** @type {gdjs.RuntimeBehavior} */", + " behavior;", + " /** @type {gdjs.RuntimeObject | null} */", + " basisObject;", + " followingDoneThisFrame = false;", + " relativeX = 0;", + " relativeY = 0;", + " relativeAngle = 0;", + " relativeRotatedX = 0;", + " relativeRotatedY = 0;", + " basisOldX = 0;", + " basisOldY = 0;", + " basisOldAngle = 0;", + " basisOldWidth = 0;", + " basisOldHeight = 0;", + " basisOldCenterXInScene = 0;", + " basisOldCenterYInScene = 0;", + "", + " /**", + " * @param {gdjs.RuntimeBehavior} behavior", + " */", + " constructor(behavior) {", + " this.behavior = behavior;", + " }", + "", + " /**", + " * Update the coordinates in the basisObject basis.", + " * ", + " * It uses the basisObject coordinates from the previous frame.", + " * This way, the sticker can move relatively to it and still", + " * follow basisObject.", + " * ", + " * @param {gdjs.RuntimeObject} basisObject", + " */", + " updateRelativeCoordinates() {", + " const object = this.behavior.owner;", + "", + " // Update relative coordinates", + " this.relativeX = object.getX() - this.basisOldX;", + " this.relativeY = object.getY() - this.basisOldY;", + " if (!this.behavior._getOnlyFollowPosition()) {", + " this.relativeAngle = object.getAngle() - this.basisOldAngle;", + " this.relativeWidth = object.getWidth() / this.basisOldWidth;", + " this.relativeHeight = object.getHeight() / this.basisOldHeight;", + " const deltaX = object.getCenterXInScene() - this.basisOldCenterXInScene;", + " const deltaY = object.getCenterYInScene() - this.basisOldCenterYInScene;", + " const angle = this.basisOldAngle * Math.PI / 180;", + " this.relativeRotatedX = (deltaX * Math.cos(angle) + deltaY * Math.sin(angle)) / this.basisOldWidth;", + " this.relativeRotatedY = (-deltaX * Math.sin(angle) + deltaY * Math.cos(angle)) / this.basisOldHeight;", + "", + " // Save initial values to avoid calculus and rounding errors", + " this.basisOriginalWidth = this.basisObject.getWidth();", + " this.basisOriginalHeight = this.basisObject.getHeight();", + " this.basisOriginalAngle = this.basisObject.getAngle();", + " }", + " }", + "", + " stickTo(basisObject) {", + " this.basisObject = basisObject;", + " this.updateOldCoordinates();", + " this.updateRelativeCoordinates();", + " }", + "", + " unstick() {", + " this.basisObject = null;", + " }", + "", + " /**", + " * Copy the coordinates to use it the next frame.", + " */", + " updateOldCoordinates() {", + " const object = this.behavior.owner;", + "", + " this.ownerOldX = object.getX();", + " this.ownerOldY = object.getY();", + "", + " this.basisOldX = this.basisObject.getX();", + " this.basisOldY = this.basisObject.getY();", + "", + " if (!this.behavior._getOnlyFollowPosition()) {", + " this.ownerOldAngle = object.getAngle();", + " this.ownerOldWidth = object.getWidth();", + " this.ownerOldHeight = object.getHeight();", + "", + " this.basisOldAngle = this.basisObject.getAngle();", + " this.basisOldWidth = this.basisObject.getWidth();", + " this.basisOldHeight = this.basisObject.getHeight();", + " this.basisOldCenterXInScene = this.basisObject.getCenterXInScene();", + " this.basisOldCenterYInScene = this.basisObject.getCenterYInScene();", + " }", + " }", + "", + " /**", + " * Follow the basisObject (called in doStepPostEvents).", + " * ", + " * This method is also called by children to ensure", + " * parents are updated first.", + " */", + " followBasisObject() {", + " if (this.followingDoneThisFrame) {", + " return;", + " }", + " this.followingDoneThisFrame = true;", + " const basisObject = this.basisObject;", + " if (basisObject) {", + " // If the behavior on the basis object has a different name,", + " // the objects will still follow their basis objects", + " // but frame delays could happen.", + " const behaviorName = this.behavior.getName();", + " if (basisObject.hasBehavior(behaviorName)) {", + " const basisBehavior = basisObject.getBehavior(behaviorName);", + " if (basisBehavior.type === this.type) {", + " // Follow parents 1st to avoid frame delays", + " basisBehavior.followBasisObject();", + " }", + " }", + "", + " const object = this.behavior.owner;", + "", + " if (this.behavior._getOnlyFollowPosition()) {", + " if (object.getX() !== this.ownerOldX", + " || object.getY() !== this.ownerOldY) {", + " this.updateRelativeCoordinates();", + " }", + "", + " if (this.basisOldX !== basisObject.getX() ||", + " this.basisOldY !== basisObject.getY()) {", + " object.setPosition(", + " basisObject.getX() + this.relativeX,", + " basisObject.getY() + this.relativeY);", + " }", + " } else {", + " if (object.getX() !== this.ownerOldX", + " || object.getY() !== this.ownerOldY", + " || object.getAngle() !== this.ownerOldAngle", + " || object.getWidth() !== this.ownerOldWidth", + " || object.getHeight() !== this.ownerOldHeight) {", + " this.updateRelativeCoordinates();", + " }", + "", + " // Follow basisObject", + " if (basisObject.getAngle() === this.basisOriginalAngle && this.basisOriginalAngle === 0) {", + " if (basisObject.getWidth() === this.basisOriginalWidth ||", + " basisObject.getHeight() === this.basisOriginalHeight) {", + " if (this.basisOldX !== basisObject.getX() ||", + " this.basisOldY !== basisObject.getY()) {", + " object.setPosition(", + " basisObject.getX() + this.relativeX,", + " basisObject.getY() + this.relativeY);", + " }", + " } else {", + " object.setPosition(", + " basisObject.getX() + this.relativeRotatedX * basisObject.getWidth(),", + " basisObject.getY() + this.relativeRotatedY * basisObject.getHeight());", + " }", + " } else {", + " object.setAngle(basisObject.getAngle() + this.relativeAngle);", + "", + " const deltaX = this.relativeRotatedX * basisObject.getWidth();", + " const deltaY = this.relativeRotatedY * basisObject.getHeight();", + " const angle = -basisObject.getAngle() * Math.PI / 180;", + " object.setX(basisObject.getCenterXInScene() + object.getX() - object.getCenterXInScene() + deltaX * Math.cos(angle) + deltaY * Math.sin(angle));", + " object.setY(basisObject.getCenterYInScene() + object.getY() - object.getCenterYInScene() - deltaX * Math.sin(angle) + deltaY * Math.cos(angle));", + " }", + " // Unproportional dimensions changes won't work as expected", + " // if the object angle is not null but nothing more can be done", + " // because there is no full affine transformation on objects.", + " if (basisObject.getWidth() !== this.basisOriginalWidth) {", + " object.setWidth(this.relativeWidth * basisObject.getWidth());", + " }", + " if (basisObject.getHeight() !== this.basisOriginalHeight) {", + " object.setHeight(this.relativeHeight * basisObject.getHeight());", + " }", + " }", + "", + " this.updateOldCoordinates();", + " }", + " }", + "}", + "", + "gdjs._stickerExtension = {", + " Sticker", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, { "description": "Check if the object is stuck to another object.", "fullName": "Is stuck to another object", @@ -58,14 +284,13 @@ "const basisObjectsLists = eventsFunctionContext.getObjectsLists(\"BasisObject\");", "", "eventsFunctionContext.returnValue = gdjs.evtTools.object.twoListsTest(", - " (stickerObject, basisObject, stickerBehaviorName) => {", - " const behavior = stickerObject.getBehavior(stickerBehaviorName);", - " return behavior.basisObject === basisObject;", + " (stickerObject, basisObject) => {", + " const sticker = stickerObject.getBehavior(stickerBehaviorName)._sticker;", + " return sticker.basisObject === basisObject;", " },", " stickerObjectsLists,", " basisObjectsLists,", - " false,", - " stickerBehaviorName", + " false", ");" ], "parameterObjects": "", @@ -107,181 +332,38 @@ "name": "onCreated", "sentence": "", "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Sticker::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::JsCode", "inlineCode": [ + "const Sticker = gdjs._stickerExtension.Sticker;", + "", "const behaviorName = eventsFunctionContext.getBehaviorName(\"Behavior\");", "const object = objects[0];", "const behavior = object.getBehavior(behaviorName);", "", - "// Set up the scene sticker objects list - if not done already.", - "runtimeScene.__allStickers = runtimeScene.__allStickers || new Set();", - "", - "// Set up the behavior extra methods - if not done already.", - "const prototype = Object.getPrototypeOf(behavior);", - "if (!prototype.updateRelativeCoordinates) {", - " // Unstick from deleted objects.", - " gdjs.registerObjectDeletedFromSceneCallback(function (runtimeScene, deletedObject) {", - " const allStickers = runtimeScene.__allStickers;", - " if (!allStickers) return;", - "", - " for (const sticker of allStickers) {", - " if (sticker.basisObject === deletedObject) {", - " if (sticker._getIsDestroyedWithParent()) {", - " sticker.owner.deleteFromScene(runtimeScene);", - " }", - " sticker.basisObject = null;", - " }", - " }", - " });", - "", - " /**", - " * Update the coordinates in the basisObject basis.", - " * ", - " * It uses the basisObject coordinates from the previous frame.", - " * This way, the sticker can move relatively to it and still", - " * follow basisObject.", - " * ", - " * @param {gdjs.RuntimeObject} basisObject", - " */", - " prototype.updateRelativeCoordinates = function (basisObject) {", - " const object = this.owner;", - "", - " // Update relative coordinates", - " this.relativeX = object.getX() - this.basisOldX;", - " this.relativeY = object.getY() - this.basisOldY;", - " if (!this._getOnlyFollowPosition()) {", - " this.relativeAngle = object.getAngle() - this.basisOldAngle;", - " this.relativeWidth = object.getWidth() / this.basisOldWidth;", - " this.relativeHeight = object.getHeight() / this.basisOldHeight;", - " const deltaX = object.getCenterXInScene() - this.basisOldCenterXInScene;", - " const deltaY = object.getCenterYInScene() - this.basisOldCenterYInScene;", - " const angle = this.basisOldAngle * Math.PI / 180;", - " this.relativeRotatedX = (deltaX * Math.cos(angle) + deltaY * Math.sin(angle)) / this.basisOldWidth;", - " this.relativeRotatedY = (-deltaX * Math.sin(angle) + deltaY * Math.cos(angle)) / this.basisOldHeight;", - "", - " // Save initial values to avoid calculus and rounding errors", - " this.basisOriginalWidth = basisObject.getWidth();", - " this.basisOriginalHeight = basisObject.getHeight();", - " this.basisOriginalAngle = basisObject.getAngle();", - " }", - " }", - "", - " /**", - " * Copy the coordinates to use it the next frame.", - " * @param basisObject {gdjs.RuntimeObject}", - " */", - " prototype.updateOldCoordinates = function (basisObject) {", - " const object = this.owner;", - "", - " this.ownerOldX = object.getX();", - " this.ownerOldY = object.getY();", - "", - " this.basisOldX = basisObject.getX();", - " this.basisOldY = basisObject.getY();", - "", - " if (!this._getOnlyFollowPosition()) {", - " this.ownerOldAngle = object.getAngle();", - " this.ownerOldWidth = object.getWidth();", - " this.ownerOldHeight = object.getHeight();", - "", - " this.basisOldAngle = basisObject.getAngle();", - " this.basisOldWidth = basisObject.getWidth();", - " this.basisOldHeight = basisObject.getHeight();", - " this.basisOldCenterXInScene = basisObject.getCenterXInScene();", - " this.basisOldCenterYInScene = basisObject.getCenterYInScene();", - " }", - " }", - " /**", - " * Follow the basisObject (called in doStepPostEvents).", - " * ", - " * This method is also called by children to ensure", - " * parents are updated first.", - " */", - " prototype.followBasisObject = function () {", - " if (this.followingDoneThisFrame) {", - " return;", - " }", - " this.followingDoneThisFrame = true;", - " /** @type {gdjs.RuntimeObject} */", - " const basisObject = this.basisObject;", - " if (basisObject) {", - " // If the behavior on the basis object has a different name,", - " // the objects will still follow their basis objects", - " // but frame delays could happen.", - " if (basisObject.hasBehavior(behaviorName)) {", - " const basisBehavior = basisObject.getBehavior(behaviorName);", - " if (basisBehavior.type === this.type) {", - " // Follow parents 1st to avoid frame delays", - " basisBehavior.followBasisObject();", - " }", - " }", - "", - " const object = this.owner;", - "", - " if (this._getOnlyFollowPosition()) {", - " if (object.getX() !== this.ownerOldX", - " || object.getY() !== this.ownerOldY) {", - " this.updateRelativeCoordinates(basisObject);", - " }", - "", - " if (this.basisOldX !== basisObject.getX() ||", - " this.basisOldY !== basisObject.getY()) {", - " object.setPosition(", - " basisObject.getX() + this.relativeX,", - " basisObject.getY() + this.relativeY);", - " }", - " } else {", - " if (object.getX() !== this.ownerOldX", - " || object.getY() !== this.ownerOldY", - " || object.getAngle() !== this.ownerOldAngle", - " || object.getWidth() !== this.ownerOldWidth", - " || object.getHeight() !== this.ownerOldHeight) {", - " this.updateRelativeCoordinates(basisObject);", - " }", - "", - " // Follow basisObject", - " if (basisObject.getAngle() === this.basisOriginalAngle && this.basisOriginalAngle === 0) {", - " if (basisObject.getWidth() === this.basisOriginalWidth ||", - " basisObject.getHeight() === this.basisOriginalHeight) {", - " if (this.basisOldX !== basisObject.getX() ||", - " this.basisOldY !== basisObject.getY()) {", - " object.setPosition(", - " basisObject.getX() + this.relativeX,", - " basisObject.getY() + this.relativeY);", - " }", - " } else {", - " object.setPosition(", - " basisObject.getX() + this.relativeRotatedX * basisObject.getWidth(),", - " basisObject.getY() + this.relativeRotatedY * basisObject.getHeight());", - " }", - " } else {", - " object.setAngle(basisObject.getAngle() + this.relativeAngle);", - "", - " const deltaX = this.relativeRotatedX * basisObject.getWidth();", - " const deltaY = this.relativeRotatedY * basisObject.getHeight();", - " const angle = -basisObject.getAngle() * Math.PI / 180;", - " object.setX(basisObject.getCenterXInScene() + object.getX() - object.getCenterXInScene() + deltaX * Math.cos(angle) + deltaY * Math.sin(angle));", - " object.setY(basisObject.getCenterYInScene() + object.getY() - object.getCenterYInScene() - deltaX * Math.sin(angle) + deltaY * Math.cos(angle));", - " }", - " // Unproportional dimensions changes won't work as expected", - " // if the object angle is not null but nothing more can be done", - " // because there is no full affine transformation on objects.", - " if (basisObject.getWidth() !== this.basisOriginalWidth) {", - " object.setWidth(this.relativeWidth * basisObject.getWidth());", - " }", - " if (basisObject.getHeight() !== this.basisOriginalHeight) {", - " object.setHeight(this.relativeHeight * basisObject.getHeight());", - " }", - " }", - "", - " this.updateOldCoordinates(basisObject);", - " }", - " }", - "}", + "behavior._sticker = new Sticker(behavior);", "", + "// Set up the scene sticker objects list - if not done already.", + "runtimeScene._stickerExtension = runtimeScene._stickerExtension || {", + " allStickers: new Set(),", + "};", "// Register this object as a sticker.", - "runtimeScene.__allStickers.add(behavior);", + "runtimeScene._stickerExtension.allStickers.add(behavior);", "" ], "parameterObjects": "Object", @@ -317,7 +399,7 @@ "const object = objects[0];", "const behavior = object.getBehavior(behaviorName);", "", - "behavior.followingDoneThisFrame = false;" + "behavior._sticker.followingDoneThisFrame = false;" ], "parameterObjects": "Object", "useStrict": true, @@ -352,7 +434,7 @@ "const object = objects[0];", "const behavior = object.getBehavior(behaviorName);", "", - "behavior.followBasisObject();" + "behavior._sticker.followBasisObject();" ], "parameterObjects": "Object", "useStrict": true, @@ -389,13 +471,9 @@ "const basisObjects = eventsFunctionContext.getObjects(\"BasisObject\");", "", "if (basisObjects.length === 0) return;", + "// An object can stick to only one object.", "const basisObject = basisObjects[0];", - "", - "const behavior = object.getBehavior(behaviorName);", - "", - "behavior.basisObject = basisObject;", - "behavior.updateOldCoordinates(basisObject);", - "behavior.updateRelativeCoordinates(basisObject);", + "object.getBehavior(behaviorName)._sticker.stickTo(basisObject);", "" ], "parameterObjects": "Object", @@ -437,7 +515,8 @@ "const behaviorName = eventsFunctionContext.getBehaviorName(\"Behavior\");", "const behavior = object.getBehavior(behaviorName);", "", - "behavior.basisObject = null;" + "object.getBehavior(behaviorName)._sticker.unstick();", + "" ], "parameterObjects": "Object", "useStrict": true, @@ -472,8 +551,7 @@ "const object = objects[0];", "const behavior = object.getBehavior(behaviorName);", "", - "if (runtimeScene.__allStickers)", - " runtimeScene.__allStickers.delete(behavior);", + "runtimeScene._stickerExtension.allStickers.delete(behavior._sticker);", "" ], "parameterObjects": "Object",